k3s auf VPS absichern: Ports, kubeconfig und Pods
Die k3s-Standardinstallation öffnet 6443, 10250, 10256 und 8472/udp. Sichern Sie kubeconfig, NodePorts und privilegierte Pods auf Ihrem VPS.
Was ein Single-Node-k3s-Cluster am ersten Tag nach außen freigibt
Ein Single-Node-k3s-Cluster auf einem öffentlichen VPS ist am Tag nach Abschluss des Einzeilen-Installers an fünf konkreten Stellen exponiert: der Kubernetes-API-Server über TCP 6443, der kubelet über TCP 10250, die auf dem Datenträger liegende kubeconfig-Datei, der NodePort-Bereich, den Ihre Firewall nicht erkennen kann, und jeder Pod, der privileged oder hostPath anfordern darf. Für jeden dieser Punkte gibt es eine Lösung, die nur wenige Minuten dauert. Diese Anleitung setzt voraus, dass k3s bereits läuft. Falls das nicht der Fall ist, beginnen Sie mit einer Single-Node-k3s-Installation auf einem VPS und kehren Sie anschließend hierher zurück.
Prüfen Sie, welche Ports auf Verbindungen warten, bevor Sie Änderungen vornehmen.
sudo ss -tulpn | grep -E '6443|10250|10256|8472'Eine Standardinstallation zeigt 6443 (den API-Server), 10250 (den kubelet), 10256 (die kube-proxy-Gesundheitsprüfung) und 8472/udp (das Flannel-Overlay, das VXLAN, Virtual Extensible LAN, verwendet). k3s bindet standardmäßig an 0.0.0.0. Daher befinden sich alle diese Ports auf Ihrer öffentlichen Adresse und nicht nur auf dem Loopback-Interface.
Warum Port 6443 den gesamten Cluster kontrolliert
Alles, was sich mit Administratorrechten an Port 6443 authentifizieren kann, kann einen Pod erstellen. Ein Pod kann root auf dem Host werden. Port 6443 ist der Zugang zum Rechner.
Ein offener Port 6443 führt nicht sofort zu einem erfolgreichen Einbruch, weil Kubernetes keine Passwörter akzeptiert. Kubernetes erwartet ein Clientzertifikat oder ein Bearer-Token. Zwei Dinge gelten trotzdem.
Erstens beantwortet der API-Server einige Anfragen ganz ohne Anmeldedaten. Das standardmäßige Kubernetes-RBAC (rollenbasierte Zugriffskontrolle) ordnet die Gruppe system:unauthenticated einer Rolle namens system:public-info-viewer zu. Diese erlaubt /version, /healthz, /livez und /readyz. Von einem anderen Rechner:
curl -sk https://YOUR_SERVER_IP:6443/versionDas gibt Ihre genaue Kubernetes-Version zurück. Sie ist die Eingabe für eine CVE-Suche (Common Vulnerabilities and Exposures) und der Grund, warum ein Scanner Ihren Rechner als interessant einstuft. Alles hinter diesen Pfaden wird abgewiesen. Die Fehlermeldung nennt dabei Ihre Identität:
forbidden: User "system:anonymous" cannot get path "/api"Zweitens ist jeder Fehler im API-Server aus der Ferne erreichbar, solange dieser Port offen ist. Patching ist dann nicht mehr optional.
Die einfache Lösung ist eine Firewall-Regel. ufw benötigt hier mehr als eine Zeile, weil k3s den Clusterverkehr über denselben Kernel leitet.
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow from 203.0.113.10 to any port 6443 proto tcp
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
sudo ufw enableDie letzten beiden Regeln stammen direkt aus der k3s-Dokumentation. 10.42.0.0/16 ist das standardmäßige Pod-Netzwerk und 10.43.0.0/16 das standardmäßige Service-Netzwerk. Ohne diese Regeln verwirft ufw den internen Clusterverkehr. Dadurch verlieren Pods den Zugriff auf den API-Server und aufeinander. Das Beispiel für k3s erlaubt Port 6443 von überall. Ersetzen Sie diese Einstellung durch Ihre eigene Adresse. Das ist die Änderung, die sich lohnt. Wenn ufw für Sie neu ist, erklärt die ufw-Grundlagen für die Firewall eines VPS die hierfür erforderlichen Standardrichtlinien.
Die k3s-Dokumentation formuliert es beim Overlay-Port deutlich: „Der VXLAN-Port auf Nodes sollte nicht für das Internet erreichbar sein, da dadurch der Zugriff beliebiger Personen auf Ihr Clusternetzwerk ermöglicht wird.“ Eine standardmäßige Deny-Richtlinie für eingehenden Datenverkehr verhindert das, ohne den Port anzugeben.
Die bessere Lösung ist, den API-Server überhaupt nicht mehr über die öffentliche Adresse zu erreichen. Verwenden Sie stattdessen eine VPN- oder Mesh-Adresse. Das Serverzertifikat muss die Adresse enthalten, zu der Sie eine Verbindung herstellen. Fügen Sie sie daher als SAN (Subject Alternative Name) in /etc/rancher/k3s/config.yaml hinzu:
tls-san:
- 10.8.0.1
- k3s.example.com
secrets-encryption: truesudo systemctl restart k3s
sudo k3s secrets-encrypt statussecrets-encryption: true verschlüsselt Secret-Objekte im Datenspeicher. Die k3s-Dokumentation weist darauf hin: „Die Verschlüsselung von Secrets kann auf einem bestehenden Server nicht aktiviert werden, ohne ihn neu zu starten.“ Secrets, die vor der Änderung geschrieben wurden, behalten ihre alte Form, bis Sie sudo k3s secrets-encrypt reencrypt ausführen. Machen Sie sich klar, was diese Funktion leistet. Sie schützt eine vom Backup kopierte Datenspeicherdatei. Gegen Personen mit Zugriff auf den API-Server schützt sie nicht, weil der API-Server Secrets für jeden entschlüsselt, der sie lesen darf. Diese Unterscheidung gilt auch für selbst gehostete Secret Stores. Deshalb konzentriert sich die Absicherung von Vaultwarden auf das Admin-Token und die Backup-Datei und nicht auf die Verschlüsselung selbst.
Warum der kubelet auf Port 10250 wichtig ist
Der kubelet ist der Agent, der Container startet. Seine API auf Port 10250 listet Pods auf und führt Befehle in ihnen aus. Ein kubelet, das anonyme Anfragen akzeptiert, ermöglicht eine Remote-Shell für jede Workload auf dem Rechner.
Prüfen Sie Ihre Konfiguration:
curl -sk https://127.0.0.1:10250/pods | head -c 60Ein aktueller k3s antwortet mit Unauthorized, weil der kubelet den API-Server auffordert, jeden Aufrufer zu authentifizieren und zu autorisieren. Wenn stattdessen eine JSON-Pod-Liste zurückkommt, ist der anonyme Zugriff aktiviert. Jeder, der Port 10250 erreichen kann, kann dann aus Ihren Containern lesen und dort Befehle ausführen.
Sperren Sie den Port in jedem Fall von außen. Auf einem einzelnen Knoten ist die Steuerungsebene der einzige Client des kubelet. Sie läuft auf demselben Rechner, und dieser Datenverkehr kommt über die Loopback-Schnittstelle, die ufw standardmäßig akzeptiert. Wenn Sie 10250 aus dem Internet sperren, entstehen Ihnen dadurch keine Nachteile. Wenn Sie wegen eines fehlerhaften kubectl top oder eines metrics-server hierher gelangt sind, der nicht stabil läuft, finden Sie die Ursachen unter Fehler mit dem kubelet-Port 10250.
Ihre kubeconfig ist eine Cluster-Administratoranmeldedaten
k3s erstellt /etc/rancher/k3s/k3s.yaml mit root als Eigentümer und dem Modus 600. In der Dokumentation wird die Folge einer Änderung beschrieben: „Die kubeconfig-Datei gehört root und wird standardmäßig mit dem Modus 600 geschrieben. Wenn Sie den Modus auf 644 ändern, können andere nicht privilegierte Benutzer auf dem Host die Datei lesen.“
Das bedeutet: Mit dem Modus 644 wird jedes lokale Konto zum Clusteradministrator. Viele Anleitungen empfehlen genau das, üblicherweise mit --write-kubeconfig-mode 644, damit kubectl ohne sudo funktioniert. Dadurch wird die Administratoranmeldedaten an jeden Benutzer mit einer Shell weitergegeben.
Kopieren Sie die Datei stattdessen für einen einzelnen Benutzer.
mkdir -p ~/.kube
sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config
kubectl get nodesPrüfen Sie anschließend, ob die ursprünglichen Berechtigungen weiterhin restriktiv sind:
stat -c '%a %U:%G' /etc/rancher/k3s/k3s.yaml600 root:root ist die gesuchte Antwort. Diese Datei enthält ein Clientzertifikat für ein Mitglied von system:masters. Diese Gruppe behandelt der API-Server grundsätzlich als berechtigt, sodass RBAC-Regeln dafür nie geprüft werden. Kubernetes verfügt über keine Zertifikatsperrliste. Eine offengelegte Kopie bleibt daher gültig, bis Sie die Zertifizierungsstelle des Clusters rotieren. Behandeln Sie die Datei wie einen privaten SSH-Schlüssel und halten Sie die Anzahl der Konten mit Zugriff darauf klein. Das entspricht demselben Grundsatz wie Benutzerkonten mit den geringsten erforderlichen Berechtigungen auf einem VPS.
Ihre Firewall erkennt keinen NodePort-Datenverkehr
Ein type: NodePort-Service öffnet auf jeder Adresse des Knotens, einschließlich der öffentlichen Adresse, einen Port zwischen 30000 und 32767. Ein type: LoadBalancer-Service geht unter k3s noch weiter: ServiceLB, der integrierte Load Balancer, plant pro Service einen kleinen Pod in kube-system ein. Dieser Pod beansprucht den Service-Port direkt auf dem Host.
kubectl -n kube-system get pods | grep svclb
sudo ss -tulpn | grep -E ':3[0-2][0-9]{3}'Jetzt kommt der Teil, der viele überrascht. Wenn Sie diesen Port mit ufw blockieren, antwortet er weiterhin.
sudo ufw deny 30080/tcp
curl http://YOUR_SERVER_IP:30080Die Seite wird weiterhin geladen, weil das Paket einen anderen Weg nimmt. kube-proxy schreibt DNAT-Regeln (Destination Network Address Translation) in die PREROUTING-Kette der nat-Tabelle. PREROUTING wird ausgeführt, bevor eine Filterentscheidung getroffen wird. Das Ziel wird in eine Pod-Adresse geändert. Diese gehört nicht zum Host. Deshalb leitet der Kernel das Paket durch die FORWARD-Kette und nie durch INPUT. Die Regeln von ufw liegen in INPUT. Das Paket trifft nicht auf sie. Die Reihenfolge der Sprünge ist sichtbar:
sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | headKUBE-SERVICES steht am Anfang von PREROUTING. Die Kubernetes-Sprünge in FORWARD stehen vor den eigenen Ketten von ufw. Das ist derselbe Mechanismus, mit dem Docker Ports an ufw vorbei veröffentlicht, und es gelten dieselben Lösungen.
- Filtern Sie den Datenverkehr in der Netzwerk-Firewall Ihres Providers. Sie wird vor der Maschine ausgeführt und berücksichtigt nicht, wie Ihr Kernel das Routing vornimmt.
- Verzichten Sie auf NodePort und LoadBalancer. Belassen Sie die Services bei
ClusterIPund erreichen Sie sie mitkubectl port-forwardüber die bereits bestehende SSH-Sitzung. - Veröffentlichen Sie einen einzigen Ingress auf 80 und 443 und nichts anderes.
- Schränken Sie den Bereich mit
service-node-port-rangeunterkube-apiserver-argein, damit ein versehentlich verwendeter NodePort in einen Bereich fällt, den Sie überwachen.
ufw ist weiterhin sinnvoll. Es steuert den Datenverkehr, der an den Host selbst adressiert ist, also SSH und den API-Server. Pod-Datenverkehr wird jedoch nicht gefiltert. Wer das erwartet, macht eine Datenbank versehentlich erreichbar.
Ein Pod mit hostPath oder privileged hat root-Zugriff auf Ihren VPS
Container sind gewöhnliche Prozesse auf Ihrem Kernel, sehen aber nur einen eingeschränkten Teil davon. Mehrere Pod-Felder heben diese Einschränkung auf.
securityContext.privileged: truegibt dem Container alle Linux-Capabilities und Zugriff auf Host-Geräte.hostPathbindet ein Host-Verzeichnis in den Pod ein. Ein Pod, der/mit Schreibzugriff einbindet, kann einen Schlüssel an/root/.ssh/authorized_keysanhängen.hostPID: truesetzt den Container in den Prozess-Namespace des Hosts. Dort öffnetnsentergegen PID 1 eine Shell auf dem Host.hostNetwork: truesetzt ihn auf den Netzwerk-Stack des Hosts. Dort kann er Host-Ports binden und Dienste erreichen, die an Loopback-Adressen gebunden sind.
Die Frage „Wer kann hier Pods erstellen?“ ist daher gleichbedeutend mit der Frage „Wer hat root-Zugriff auf diesen VPS?“. Jeder ServiceAccount mit create auf Pods in einem beliebigen Namespace ist root-gleichwertig, sofern nicht zuvor eine andere Komponente den Pod ablehnt.
Diese Aufgabe übernimmt die Pod-Security-Admission, die in den API-Server integriert ist. Die schnelle Variante verwendet ein Label pro Namespace und erfordert keinen Neustart.
kubectl label namespace default \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restrictedbaseline lehnt alle vier oben genannten Felder ab. restricted geht weiter und verlangt einen Nicht-root-Benutzer, ein seccomp-Profil (Secure Computing Mode), keine Rechteausweitung und auf ALL reduzierte Capabilities. Dadurch funktionieren viele veröffentlichte Charts nicht mehr. Wenn Sie baseline erzwingen und bei restricted zunächst nur Warnungen aktivieren, können Sie vor der Umstellung prüfen, was dadurch nicht mehr funktioniert.
Überprüfen Sie die Konfiguration:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pstest
spec:
containers:
- name: app
image: busybox
command: ["sleep", "60"]
securityContext:
privileged: true
EOFDer API-Server lehnt den Pod ab und nennt das abgelehnte Feld:
Error from server (Forbidden): error when creating "STDIN": pods "pstest" is forbidden: violates PodSecurity "baseline:latest": privileged (container "app" must not set securityContext.privileged=true)Für einen clusterweiten Standard anstelle eines Labels in jedem Namespace dokumentiert k3s eine Admission-Konfigurationsdatei unter /var/lib/rancher/k3s/server/psa.yaml:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1beta1
kind: PodSecurityConfiguration
defaults:
enforce: "baseline"
enforce-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
namespaces: [kube-system]Verweisen Sie den API-Server in /etc/rancher/k3s/config.yaml auf diese Datei und starten Sie k3s anschließend neu:
kube-apiserver-arg:
- 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'Die Ausnahme für kube-system ist erforderlich. Die eigenen ServiceLB-Pods von k3s beanspruchen Host-Ports, was baseline verbietet. Wenn kube-system in der Liste fehlt, werden diese Pods beim nächsten Neuerstellen abgelehnt. Lassen Sie eine zweite SSH-Sitzung geöffnet, wenn Sie k3s nach einer Änderung der Admission-Konfiguration neu starten.
Eine weitere nützliche Funktion: k3s liefert einen Network-Policy-Controller mit und aktiviert ihn standardmäßig. Daher wirken NetworkPolicy-Objekte in diesem Cluster ohne zusätzliche Installation. Das gilt nicht für jede Kubernetes-Distribution. Network Policies verhindern, dass ein kompromittierter Pod die übrigen Komponenten erreicht.
Nicht verwendete gebündelte Komponenten entfernen
Das Installationsprogramm stellt eine Reihe von Add-ons bereit. Jede Komponente öffnet einen Listener und erhöht den Patchaufwand. --disable akzeptiert diese Werte: coredns, servicelb, traefik, local-storage, metrics-server, runtimes.
Behalten Sie coredns. Ohne diese Komponente kann im Cluster kein Name aufgelöst werden. Die übrigen Komponenten sind optional. In /etc/rancher/k3s/config.yaml:
disable:
- traefik
- servicelb
disable-helm-controller: truesudo systemctl restart k3s
kubectl get pods -Ak3s löscht deaktivierte Komponenten. Daher verschwinden die traefik-Pods und die svclb--Pods automatisch. Machen Sie sich vorher mit den Folgen vertraut. Wenn servicelb fehlt, bleibt jeder type: LoadBalancer-Dienst dauerhaft auf <pending>, weil ihm keine Adresse zugewiesen wird. Wenn traefik fehlt, gibt es keinen Ingress-Controller. Daher haben Ingress-Objekte keinerlei Wirkung. Deaktivieren Sie diese Komponenten, wenn Sie den Datenverkehr auf andere Weise bedienen, beispielsweise über einen Reverse Proxy auf dem Host. Wenn Sie sie verwenden, lassen Sie sie aktiviert. disable-helm-controller: true entfernt den Controller, der HelmChart-Ressourcen überwacht. Dabei handelt es sich um eine privilegierte Komponente, die Sie nicht benötigen, wenn Sie helm selbst ausführen.
Automatisches Einbinden des standardmäßigen ServiceAccount-Tokens deaktivieren
Jeder Pod erhält standardmäßig unter /var/run/secrets/kubernetes.io/serviceaccount/token ein ServiceAccount-Token, sofern Sie dies nicht anders festlegen. Der default-ServiceAccount verfügt über keine RBAC-Berechtigungen. Das Token allein bewirkt daher nicht viel. Für einen Angreifer in einem kompromittierten Container stellt es jedoch ein gültiges Authentifizierungsmerkmal und einen erreichbaren API-Server bereit. Das ist der erste Schritt in den meisten Anleitungen zur Cluster-Eskalation.
Der k3s-Hardening-Leitfaden deaktiviert dies pro Namespace:
kubectl patch serviceaccount --namespace default default --patch '{"automountServiceAccountToken": false}'
kubectl patch serviceaccount --namespace kube-node-lease default --patch '{"automountServiceAccountToken": false}'
kubectl patch serviceaccount --namespace kube-public default --patch '{"automountServiceAccountToken": false}'Prüfen Sie, ob die Änderung wirksam ist. Warten Sie zunächst einige Sekunden, bis der Pod gestartet ist.
kubectl run t --image=busybox --restart=Never --command -- sleep 30
kubectl exec t -- ls /var/run/secrets/kubernetes.io/serviceaccountDer Pfad ist nicht mehr vorhanden. Daher gibt ls No such file or directory aus. Ein Workload, der tatsächlich API-Zugriff benötigt, setzt automountServiceAccountToken: true in seiner eigenen Pod-Spezifikation. Der Zugriff wird also nicht dauerhaft für alle Workloads gesperrt. Das ist bereits ein kleiner Sicherheitsgewinn. Der größere Gewinn besteht darin, einem Workload keinen ServiceAccount mit echten Berechtigungen zuzuweisen. Welchen Wert ein Token derzeit hat, können Sie wie folgt prüfen:
kubectl auth can-i --list --as=system:serviceaccount:default:defaultRessourcenlimits, damit ein einzelner Pod den Cluster nicht außer Betrieb setzt
Auf einem einzelnen Node teilen sich die Control Plane und Ihre Workloads denselben Kernel und denselben Arbeitsspeicher. Ein Pod mit einem Speicherleck wird nicht immer allein beendet. Der OOM-Killer (out of memory) des Kernels wählt sein Opfer anhand eines Scores aus, der große Prozesse stärker gewichtet. k3s ist ein großer, langlebiger Prozess. Deshalb kann statt des Pods der Cluster verschwinden. Ihre Workloads werden dann nicht neu gestartet, weil der Prozess, der die Workloads neu startet, selbst beendet wurde.
Eine LimitRange ergänzt Limits für Pods, die keine festlegen:
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: default
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 50m
memory: 128MiEine ResourceQuota begrenzt, wie viele Ressourcen der gesamte Namespace beanspruchen kann:
apiVersion: v1
kind: ResourceQuota
metadata:
name: cap
namespace: default
spec:
hard:
limits.cpu: "3"
limits.memory: 3Gi
pods: "20"Reservieren Sie anschließend in /etc/rancher/k3s/config.yaml Speicher für k3s:
kubelet-arg:
- 'system-reserved=cpu=250m,memory=512Mi'Der Unterschied zeigt sich an zwei Stellen. Ein Container, der wegen Überschreitung seines eigenen Limits beendet wurde, meldet unter Last State in kubectl describe pod den Eintrag Reason: OOMKilled. Der restliche Node läuft weiter. Wenn der Arbeitsspeicher des Nodes vollständig erschöpft war, erscheint in dmesg normalerweise eine Zeile mit Killed process. Dabei sind meist auch benachbarte Workloads betroffen. Im ersten Fall arbeitet Ihr Limit wie vorgesehen. Den zweiten Fall sollen Limits verhindern.
Manifeste in CI und im k3s-Cluster regelmäßig prüfen
Scans gehören an zwei Stellen. Sie erkennen unterschiedliche Probleme. Installieren Sie zuerst Trivy. Das Projekt veröffentlicht mit jeder Version ein Debian-Paket. Im August 2026 war 0.74.0 aktuell. Prüfen Sie daher vor der festen Aufnahme in die Automatisierung die Releases-Seite auf eine neuere Version.
sudo apt-get install -y wget
wget -q https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.deb
sudo dpkg -i trivy_0.74.0_Linux-64bit.deb
trivy --versionDie erste Stelle sind Ihre Manifeste, bevor sie den Cluster erreichen.
trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deploy--exit-code 1 lässt den CI-(Continuous-Integration-)Job bei einem Fund fehlschlagen. Jedes Ergebnis nennt das fehlerhafte Feld und dessen Schweregrad. Dadurch schlägt ein privileged: true, das Sie nicht einchecken wollten, beim Build fehl, statt den API-Server zu erreichen. Einen Fund, den Sie bewusst akzeptieren, tragen Sie in eine .trivyignore-Datei ein. Damit bleibt diese Entscheidung in git neben dem verursachenden Manifest erhalten.
Die zweite Stelle ist der laufende Cluster, und zwar nach einem Zeitplan.
trivy k8s --compliance=k8s-cis-1.23 --report summaryEin wichtiger Hinweis zu diesem Befehl: trivy k8s stellt einen Node-Collector-Pod bereit, der Hostzugriff benötigt, um Einstellungen auf Node-Ebene zu prüfen. Wenn Sie gerade begonnen haben, privilegierte Pods abzulehnen, sollten Sie das berücksichtigen und nicht umgehen. trivy k8s --report summary --disable-node-collector überspringt den Collector. Damit entfallen auch die Prüfungen auf Node-Ebene.
kube-bench deckt die Host-Seite ab: Dateiberechtigungen und Prozess-Flags. Dort liegt der größte Teil des eigentlichen CIS-(Center-for-Internet-Security-)Benchmarks. Das Tool enthält ein k3s-Profil. Verwenden Sie das vorgelagerte Job-Manifest und passen Sie es an.
curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yamlÄndern Sie den Container-Befehl in ["kube-bench", "--benchmark", "k3s-cis-1.7"]. Ersetzen Sie außerdem die Mounts /etc/kubernetes und /var/lib/etcd durch /etc/rancher und /var/lib/rancher, weil k3s seine Dateien dort ablegt. Anschließend:
kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1Beachten Sie, was dieser Job ist: hostPID: true plus Mounts von Host-Verzeichnissen. Genau diese Pod-Struktur haben Sie vor einem Abschnitt abzulehnen begonnen. Führen Sie ihn im ausgenommenen Namespace kube-system aus, lesen Sie die Ausgabe und führen Sie anschließend kubectl delete job kube-bench aus. Ein Scanner, der privilegiert laufen muss, ist kein Grund, privilegierte Workloads nicht mehr generell zu verbieten.
Der Inhalt von Images ist eine separate Frage. trivy image ghcr.io/example/app:1.4 liest die Paketdatenbank innerhalb eines Images und listet bekannte Schwachstellen auf. Das ist dieselbe Aufgabe wie beim Prüfen Ihres Servers auf bekannte CVEs.
Nun zum wichtigen Teil der Scanner-Ausgabe: Ein einzelner Hobby-Cluster schlägt bei einer langen Liste von CIS-Kontrollen fehl. Die meisten dieser Fehler sind sowohl korrekt als auch für Sie irrelevant. Der Benchmark ist für einen Cluster mit mehreren Nodes und Mandanten ausgelegt: etcd auf getrennten Hosts, vom Rechner weggeschickte Audit-Logs, eine separate Zertifizierungsstelle für kubelet und Admission-Plugins für ein Compliance-Regelwerk, dem Sie nicht unterliegen. k3s führt die Control Plane absichtlich als einen Prozess mit einer Konfigurationsdatei aus. Deshalb können Kontrollen, die die Berechtigungen einer kube-scheduler-Manifestdatei prüfen, nicht erfolgreich sein, weil eine solche Datei nicht existiert.
Lesen Sie die Fehler in dieser Reihenfolge und hören Sie auf, sobald der Nutzen erschöpft ist: Dateimodi und Eigentümer unter /etc/rancher und /var/lib/rancher, alles, was anonymen oder nicht authentifizierten Zugriff erwähnt, alles, was eine an 0.0.0.0 gebundene Komponente meldet, sowie jeden Container, der ohne nachvollziehbaren Grund als UID 0 läuft. Der Rest kann warten, bis ein zweiter Node oder eine zweite Person mit Zugriff verfügbar ist. Ein hundertzeiliger Bericht, den Sie ignorieren, ist weniger wert als ein fünfzeiliger Bericht, auf den Sie reagieren.
Eine Reihenfolge zur Absicherung von k3s auf einem einzelnen Knoten
- Setzen Sie die standardmäßige eingehende ufw-Richtlinie auf deny, erlauben Sie SSH, erlauben Sie 6443 von Ihrer eigenen Adresse und erlauben Sie die Pod- und Servicenetzwerke.
- Kopieren Sie die kubeconfig mit dem Modus 600 in Ihr Benutzerkonto und setzen Sie niemals
--write-kubeconfig-mode 644. - Bestätigen Sie, dass der kubelet an 10250 anonyme Anfragen ablehnt, und halten Sie den Port vom Internet fern.
- Deaktivieren Sie die gebündelten Komponenten, die Sie nicht verwenden, und starten Sie k3s anschließend neu.
- Kennzeichnen Sie Ihre Namespaces für die Pod-Sicherheitsaufnahme mit
enforce=baselineundwarn=restricted. - Deaktivieren Sie das automatische Einhängen von Standard-ServiceAccount-Token.
- Fügen Sie eine LimitRange und eine ResourceQuota hinzu und reservieren Sie CPU und Arbeitsspeicher für k3s.
- Nehmen Sie
trivy fs --scanners misconfigin die CI auf und führen Sie monatlich einen CIS-Scan aus.
Der zugrunde liegende Host benötigt weiterhin dieselbe Sorgfalt wie jeder andere Server. Bei k3s kommt eine Besonderheit hinzu. k3s wurde durch ein Skript und nicht durch apt installiert, daher wird es von apt upgrade nie berücksichtigt. Halten Sie das Betriebssystem nach seinem eigenen Zeitplan aktuell, beispielsweise mit unbeaufsichtigten Upgrades unter Ubuntu, und aktualisieren Sie k3s bewusst, indem Sie das Installationsprogramm mit dem gewünschten Channel oder der gewünschten Version erneut ausführen. Zwei Update-Pfade auf einem Rechner werden leicht übersehen. Notieren Sie daher, welcher Zeitplan für welche Komponente gilt.
FAQ
Ist es sicher, den k3s-API-Server über Port 6443 im Internet bereitzustellen?
Das ist nicht automatisch ein offener Zugang, weil der API-Server ein Clientzertifikat oder ein Token verlangt und alles andere mit forbidden: User "system:anonymous" ablehnt. Zwei Risiken bleiben bestehen. Anonyme Aufrufer können weiterhin /version lesen. Dadurch erfährt ein Scanner genau, nach welchem Kubernetes-Release er suchen muss. Außerdem wird jede zukünftige Sicherheitslücke im API-Server aus der Ferne erreichbar, solange der Port offen ist. In einem Single-Node-Cluster benötigt außerhalb Ihres eigenen kubectl niemand Port 6443. Erlauben Sie den Zugriff daher von Ihrer Adresse mit sudo ufw allow from YOUR_IP to any port 6443 proto tcp und überlassen Sie alles andere der standardmäßigen Deny-Richtlinie.
Warum blockiert meine ufw-Regel einen NodePort-Dienst nicht?
Weil das Paket die Chain, in der Ihre Regel sitzt, nicht erreicht. kube-proxy legt DNAT-Regeln in der PREROUTING-Chain der nat-Tabelle an. Diese wird zuerst ausgeführt und schreibt das Ziel auf eine Pod-Adresse um. Das Paket wird anschließend weitergeleitet, statt lokal zugestellt zu werden. Es durchläuft daher FORWARD und überspringt INPUT, wo die Regeln von ufw liegen. Bestätigen Sie dies mit sudo iptables -S PREROUTING -t nat | head. Filtern Sie NodePorts in der Netzwerk-Firewall Ihres Providers oder vermeiden Sie type: NodePort und greifen Sie stattdessen mit kubectl port-forward auf die Dienste zu.
Sollte ich k3s mit --write-kubeconfig-mode 644 ausführen?
Nein. Die k3s-Dokumentation beschreibt die Wirkung eindeutig: "Changing the mode to 644 will allow it to be read by other unprivileged users on the host." Die Datei enthält ein Clientzertifikat für system:masters. Bei Modus 644 wird dadurch jedes lokale Konto zum Clusteradministrator. Kopieren Sie die Datei stattdessen mit sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config für einen einzelnen Benutzer und belassen Sie das Original bei 600 root:root.
Welchen kube-bench-Benchmark sollte ich für k3s verwenden?
Verwenden Sie k3s-cis-1.7. In der kube-bench-Dokumentation steht: "kube-bench includes benchmarks for Rancher K3S platform. To run this you will need to specify --benchmark k3s-cis-1.7 when you run the kube-bench command." Übergeben Sie den Benchmark explizit, weil die automatische Erkennung von einem kubeadm-Layout ausgeht, während k3s seine Dateien unter /etc/rancher und /var/lib/rancher ablegt. Rechnen Sie mit Fehlern, die für einen einzelnen Knoten nicht relevant sind. Prüfen Sie zuerst Dateiberechtigungen und anonymen Zugriff.
Bricht Pod-Security-Admission die mitgelieferten k3s-Komponenten?
Ja, wenn Sie sie für kube-system erzwingen. Die von k3s für type: LoadBalancer-Dienste erstellten ServiceLB-Pods beanspruchen Ports auf dem Host. baseline verbietet Host-Ports. Daher werden diese Pods bei ihrer nächsten Neuerstellung abgelehnt. Nehmen Sie kube-system in der Admission-Konfigurationsdatei aus oder wenden Sie Pod Security nur als Labels auf Namespaces an, die Sie verwalten. Beginnen Sie mit enforce=baseline und warn=restricted. So können Sie lesen, was restricted beschädigen würde, bevor Sie die Richtlinie aktivieren.