Jak zabezpieczyć jednowęzłowy klaster k3s na VPS
Dowiedz się, jak uszczelnić k3s na publicznym serwerze. Wyjaśniamy, jak ograniczyć dostęp do portów 6443 i 10250, zmienić uprawnienia kubeconfig oraz zablokować ryzykowne pody.
Co ujawnia jednowęzłowy klaster k3s w dniu uruchomienia
Jednowęzłowy klaster k3s na publicznym serwerze VPS jest narażony w pięciu konkretnych punktach dzień po zakończeniu instalacji jednowierszowej: serwer API Kubernetes na porcie TCP 6443, kubelet na porcie TCP 10250, plik kubeconfig znajdujący się na dysku, zakres portów NodePort, którego nie widzi firewall, oraz każdy pod, który może zażądać privileged lub hostPath. Każdy z tych punktów można zabezpieczyć w kilka minut. Niniejszy przewodnik zakłada, że k3s jest już uruchomiony, więc jeśli tak nie jest, należy zacząć od instalacji jednowęzłowego k3s na VPS i wrócić tutaj po zakończeniu.
Przed wprowadzeniem jakichkolwiek zmian należy sprawdzić, co nasłuchuje na portach.
sudo ss -tulpn | grep -E '6443|10250|10256|8472'Domyślna instalacja wykazuje porty 6443 (serwer API), 10250 (kubelet), 10256 (kontrola stanu kube-proxy) oraz 8472/udp (nakładka flannel, która wykorzystuje VXLAN, czyli virtual extensible LAN). k3s domyślnie wiąże się z 0.0.0.0, więc każdy z tych portów jest dostępny pod publicznym adresem IP, a nie tylko na interfejsie loopback.
Dlaczego port 6443 oznacza cały klaster
Każdy podmiot, który może uwierzytelnić się na porcie 6443 z uprawnieniami administratora, może utworzyć pod, a pod może uzyskać uprawnienia root na hoście. Port 6443 to brama do maszyny.
Otwarty port 6443 nie oznacza natychmiastowego przejęcia, ponieważ Kubernetes nie akceptuje haseł. Wymaga certyfikatu klienta lub tokena bearer. Dwie kwestie pozostają jednak aktualne.
Po pierwsze, serwer API odpowiada na niektóre żądania bez żadnych poświadczeń. Domyślny mechanizm RBAC (role-based access control) w Kubernetes przypisuje grupę system:unauthenticated do roli o nazwie system:public-info-viewer, która zezwala na /version, /healthz, /livez oraz /readyz. Z innej maszyny:
curl -sk https://YOUR_SERVER_IP:6443/versionZwraca to dokładną wersję Kubernetes, która stanowi dane wejściowe dla wyszukiwania CVE (common vulnerabilities and exposures) i jest powodem, dla którego skaner uznaje serwer za interesujący cel. Każde żądanie wykraczające poza te ścieżki jest odrzucane, a odmowa identyfikuje użytkownika:
forbidden: User "system:anonymous" cannot get path "/api"Po drugie, każdy błąd w serwerze API jest zdalnie osiągalny, dopóki port pozostaje otwarty. W tym momencie instalacja poprawek przestaje być opcjonalna.
Tanim rozwiązaniem jest reguła zapory sieciowej. Narzędzie ufw wymaga tutaj więcej niż jednej linii, ponieważ k3s kieruje ruch klastra przez to samo jądro.
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 enableDwie ostatnie reguły pochodzą bezpośrednio z dokumentacji k3s. 10.42.0.0/16 to domyślna sieć podów, a 10.43.0.0/16 to domyślna sieć usług. Bez nich ufw odrzuca ruch wewnątrz klastra, przez co pody tracą dostęp do serwera API oraz do siebie nawzajem. Przykład k3s zezwala na dostęp do portu 6443 z każdego miejsca; zastąpienie tego własnym adresem IP jest zalecaną zmianą. Jeśli ufw jest nowym narzędziem, podstawy zapory ufw dla VPS opisują domyślne polityki, od których zależy to rozwiązanie.
Dokumentacja k3s jasno określa kwestię portu overlay: "Port VXLAN na węzłach nie powinien być wystawiony na świat, ponieważ otwiera to sieć klastra na dostęp dla osób niepowołanych". Domyślna polityka odrzucania ruchu przychodzącego rozwiązuje ten problem bez konieczności wskazywania konkretnego portu.
Skuteczniejszym rozwiązaniem jest całkowite zrezygnowanie z dostępu do API przez adres publiczny na rzecz VPN lub sieci typu mesh. Certyfikat serwera musi zawierać adres, z którym następuje połączenie, dlatego należy dodać go jako SAN (subject alternative name) w /etc/rancher/k3s/config.yaml:
tls-san:
- 10.8.0.1
- k3s.example.com
secrets-encryption: truesudo systemctl restart k3s
sudo k3s secrets-encrypt statussecrets-encryption: true szyfruje obiekty Secret w magazynie danych. Dokumentacja k3s zaznacza, że "szyfrowania sekretów nie można włączyć na działającym serwerze bez jego restartu", a sekrety zapisane przed tą zmianą zachowują swoją starą formę, dopóki nie zostanie wykonane polecenie sudo k3s secrets-encrypt reencrypt. Należy mieć świadomość, co to daje. Chroni to plik magazynu danych skopiowany z kopii zapasowej. Nie zabezpiecza to przed osobą, która może komunikować się z serwerem API, ponieważ serwer API deszyfruje sekrety dla każdego, kto ma uprawnienia do ich odczytu. To samo rozróżnienie dotyczy każdego samodzielnie hostowanego magazynu sekretów, dlatego proces utwardzania Vaultwarden skupia się na tokenie administratora i pliku kopii zapasowej, a nie na samym szyfrowaniu.
Dlaczego kubelet na porcie 10250 jest istotny
Kubelet to agent odpowiedzialny za uruchamianie kontenerów. Jego API na porcie 10250 umożliwia listowanie podów oraz wykonywanie poleceń wewnątrz nich. Kubelet akceptujący anonimowe żądania stanowi zdalną powłokę (remote shell) dla każdego obciążenia uruchomionego na maszynie.
Sprawdź swoją konfigurację:
curl -sk https://127.0.0.1:10250/pods | head -c 60Aktualny k3s odpowiada Unauthorized, ponieważ kubelet wymaga od API server uwierzytelnienia i autoryzacji każdego wywołującego. Jeśli w odpowiedzi otrzymasz listę podów w formacie JSON, oznacza to, że dostęp anonimowy jest włączony, a każda osoba mająca dostęp do portu 10250 może odczytywać dane z kontenerów oraz wykonywać w nich polecenia.
W każdym przypadku zablokuj ten port od strony sieci zewnętrznej. Na pojedynczym węźle jedynym klientem kubelet jest control plane działający na tej samej maszynie, a ruch ten odbywa się przez interfejs loopback, który ufw domyślnie akceptuje. Zablokowanie portu 10250 dla ruchu z Internetu nie wiąże się z żadnymi negatywnymi skutkami. Jeśli trafiłeś tutaj z powodu niedziałającego kubectl top lub problemów z metrics-server, przyczyny zostały zebrane w sekcji błędy portu 10250 kubelet.
Plik kubeconfig to poświadczenie administratora klastra
k3s tworzy plik /etc/rancher/k3s/k3s.yaml, którego właścicielem jest root, a uprawnienia ustawione są na 600. Dokumentacja wskazuje na konsekwencje zmiany tego stanu: „Plik kubeconfig jest własnością roota i jest zapisywany z domyślnymi uprawnieniami 600. Zmiana uprawnień na 644 pozwoli na odczyt pliku przez innych nieuprzywilejowanych użytkowników w systemie”.
Należy to rozumieć następująco: uprawnienia 644 czynią każde lokalne konto administratorem klastra. Wiele poradników sugeruje dokładnie takie rozwiązanie, zazwyczaj jako --write-kubeconfig-mode 644, aby polecenie kubectl działało bez sudo. Mechanizm ten polega na przekazaniu poświadczeń administratora każdemu użytkownikowi posiadającemu dostęp do powłoki.
Zamiast tego należy skopiować plik dla wybranego użytkownika.
mkdir -p ~/.kube
sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config
kubectl get nodesNastępnie należy potwierdzić, że oryginalny plik zachowuje restrykcyjne uprawnienia:
stat -c '%a %U:%G' /etc/rancher/k3s/k3s.yaml600 root:root to pożądany wynik. Plik ten zawiera certyfikat klienta dla członka grupy system:masters, którą serwer API traktuje jako bezwarunkowo uprawnioną, co oznacza, że reguły RBAC nie są dla niej sprawdzane. Kubernetes nie posiada listy unieważnionych certyfikatów, co oznacza, że wyciekła kopia pozostaje ważna do momentu rotacji urzędu certyfikacji klastra. Należy traktować ten plik jak klucz prywatny SSH i ograniczyć liczbę kont, które mają do niego dostęp, co jest zgodne z zasadą kont użytkowników z minimalnymi uprawnieniami na VPS.
Firewall nie widzi ruchu NodePort
Usługa type: NodePort otwiera port z zakresu od 30000 do 32767 na każdym adresie przypisanym do węzła, w tym na adresie publicznym. Usługa type: LoadBalancer w k3s działa jeszcze szerzej: ServiceLB, wbudowany moduł równoważenia obciążenia, planuje mały pod dla każdej usługi w kube-system, który bezpośrednio zajmuje port usługi na hoście.
kubectl -n kube-system get pods | grep svclb
sudo ss -tulpn | grep -E ':3[0-2][0-9]{3}'Oto kwestia, która często zaskakuje. Zablokowanie tego portu za pomocą ufw nie powoduje przerwania odpowiedzi.
sudo ufw deny 30080/tcp
curl http://YOUR_SERVER_IP:30080Strona nadal się ładuje, ponieważ wynika to ze ścieżki pakietu. kube-proxy zapisuje reguły DNAT (destination network address translation) w łańcuchu PREROUTING tabeli nat, a PREROUTING jest przetwarzany przed podjęciem jakiejkolwiek decyzji o filtrowaniu. Adresem docelowym staje się adres poda, a nie hosta, więc jądro kieruje pakiet do łańcucha FORWARD, pomijając łańcuch INPUT. Reguły ufw znajdują się w INPUT. Pakiet nigdy do nich nie dociera. Kolejność skoków jest widoczna:
sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | headKUBE-SERVICES znajduje się na początku PREROUTING, a skoki Kubernetes w łańcuchu FORWARD znajdują się powyżej własnych łańcuchów ufw. Jest to ten sam mechanizm, który pozwala Dockerowi publikować porty z pominięciem ufw, dlatego rozwiązania są identyczne.
- Filtruj ruch na poziomie firewalla sieciowego u dostawcy. Działa on przed maszyną i nie zależy od sposobu routingu w jądrze.
- Zrezygnuj z NodePort i LoadBalancer. Pozostaw usługi jako
ClusterIPi uzyskuj do nich dostęp za pomocąkubectl port-forwardpoprzez istniejącą sesję SSH. - Wystaw jeden ingress na portach 80 i 443, nic więcej.
- Ogranicz zakres za pomocą
service-node-port-rangew plikukube-apiserver-arg, aby przypadkowy NodePort trafił w monitorowany obszar.
Nadal warto korzystać z ufw. Zarządza on ruchem kierowanym do samego hosta, czyli SSH oraz serwera API. Po prostu nie kontroluje on ruchu podów, a oczekiwanie, że będzie to robił, prowadzi do sytuacji, w której baza danych staje się dostępna z zewnątrz.
Pod z hostPath lub uprawnieniami privileged ma dostęp root do VPS
Kontenery to zwykłe procesy w jądrze systemu z ograniczonym widokiem na nie. Kilka pól w specyfikacji poda znosi te ograniczenia.
securityContext.privileged: truenadaje kontenerowi wszystkie uprawnienia Linux capabilities oraz dostęp do urządzeń hosta.hostPathmontuje katalog hosta wewnątrz poda. Pod, który montuje/w trybie odczytu i zapisu, może dopisać klucz do/root/.ssh/authorized_keys.hostPID: trueumieszcza kontener w przestrzeni nazw procesów hosta, gdziensenterwzględem PID 1 otwiera powłokę hosta.hostNetwork: trueumieszcza kontener w stosie sieciowym hosta, co pozwala na wiązanie portów hosta i dostęp do usług nasłuchujących na loopback.
Zatem pytanie „kto może tworzyć pody” jest równoważne z pytaniem „kto ma uprawnienia root na tym VPS”. Każde konto ServiceAccount z uprawnieniem create do tworzenia podów w dowolnej przestrzeni nazw jest równoważne z kontem root, chyba że mechanizm zabezpieczający odrzuci taki pod.
Tym mechanizmem jest Pod Security admission, wbudowany w API server. Najszybsza metoda polega na dodaniu etykiety do przestrzeni nazw i nie wymaga restartu.
kubectl label namespace default \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restrictedbaseline odrzuca wszystkie cztery wymienione wyżej pola. restricted idzie dalej i wymaga użytkownika niebędącego rootem, profilu seccomp (secure computing mode), braku eskalacji uprawnień oraz ograniczenia capabilities do ALL, co psuje wiele gotowych wykresów (charts). Wymuszenie baseline przy jednoczesnym ustawieniu ostrzegania dla restricted pozwala sprawdzić, co przestanie działać, zanim zatwierdzisz zmiany.
Weryfikacja działania:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pstest
spec:
containers:
- name: app
image: busybox
command: ["sleep", "60"]
securityContext:
privileged: true
EOFAPI server odrzuci żądanie i wskaże pole, które spowodowało błąd:
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)Aby ustawić domyślne zabezpieczenia dla całego klastra zamiast etykietowania każdej przestrzeni nazw, k3s dokumentuje plik konfiguracyjny admission pod ścieżką /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]Wskaż ten plik w /etc/rancher/k3s/config.yaml, a następnie zrestartuj k3s:
kube-apiserver-arg:
- 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'Wyjątek kube-system jest obowiązkowy. Własne pody ServiceLB w k3s wymagają portów hosta, co baseline zabrania, więc pominięcie kube-system na liście spowoduje odrzucenie tych podów przy próbie ich odtworzenia. Podczas restartu k3s po zmianie konfiguracji admission zawsze utrzymuj otwartą drugą sesję SSH.
Dodatkowa uwaga: k3s dostarcza kontroler polityk sieciowych i włącza go domyślnie, więc obiekty NetworkPolicy działają w tym klastrze bez instalowania dodatkowych komponentów. Nie jest to standardem w każdej dystrybucji Kubernetes, a jest to narzędzie pozwalające powstrzymać zainfekowany pod przed uzyskaniem dostępu do reszty infrastruktury.
Usuwanie nieużywanych komponentów pakietowych
Instalator wdraża zestaw dodatków. Każdy z nich dodaje proces nasłuchujący oraz kolejny element wymagający aktualizacji zabezpieczeń. --disable przyjmuje następujące wartości: coredns, servicelb, traefik, local-storage, metrics-server, runtimes.
Należy zachować coredns. Bez tego komponentu żaden element w klastrze nie rozwiąże nazwy. Pozostałe opcje są opcjonalne. W pliku /etc/rancher/k3s/config.yaml:
disable:
- traefik
- servicelb
disable-helm-controller: truesudo systemctl restart k3s
kubectl get pods -Ak3s usuwa wyłączone komponenty, więc pody traefik oraz pody svclb- znikną automatycznie. Należy najpierw poznać konsekwencje tych działań. Po usunięciu servicelb, każda usługa typu type: LoadBalancer pozostanie na stałe w stanie <pending>, ponieważ żaden mechanizm nie przypisze jej adresu. Po usunięciu traefik w klastrze zabraknie kontrolera ruchu przychodzącego (ingress controller), przez co obiekty Ingress przestaną działać. Komponenty te należy wyłączać tylko wtedy, gdy ruch jest obsługiwany w inny sposób, na przykład przez reverse proxy na hoście; w przeciwnym razie należy pozostawić je w spokoju. disable-helm-controller: true usuwa kontroler monitorujący zasoby HelmChart. Jest to komponent uprzywilejowany, który nie jest potrzebny, jeśli użytkownik samodzielnie zarządza helm.
Wyłączenie automatycznego montowania domyślnego tokenu ServiceAccount
Każdy pod otrzymuje token ServiceAccount w lokalizacji /var/run/secrets/kubernetes.io/serviceaccount/token, chyba że skonfigurowano inaczej. Domyślne konto default nie posiada żadnych uprawnień RBAC, więc sam token nie daje szerokich możliwości. Atakującemu wewnątrz przejętego kontenera zapewnia on jednak poprawne poświadczenia oraz dostępność API server, co stanowi pierwszy krok w większości scenariuszy eskalacji uprawnień w klastrze.
Przewodnik hardeningu k3s zaleca wyłączenie tej funkcji dla każdego 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}'Sprawdź, czy zmiana została zastosowana. Odczekaj kilka sekund, aż pod się uruchomi.
kubectl run t --image=busybox --restart=Never --command -- sleep 30
kubectl exec t -- ls /var/run/secrets/kubernetes.io/serviceaccountŚcieżka nie istnieje, więc polecenie ls zwraca No such file or directory. Obciążenie, które faktycznie wymaga dostępu do API, może ustawić automountServiceAccountToken: true we własnej specyfikacji poda, dzięki czemu żadna usługa nie zostaje trwale zablokowana. Jest to niewielkie, ale istotne zabezpieczenie. Znacznie ważniejsze jest nieprzypisywanie obciążeniom kont ServiceAccount z rzeczywistymi uprawnieniami. Możesz sprawdzić, jakie uprawnienia posiada obecnie dany token:
kubectl auth can-i --list --as=system:serviceaccount:default:defaultLimity zasobów, aby jeden pod nie spowodował awarii klastra
Na pojedynczym węźle płaszczyzna sterowania (control plane) oraz obciążenia współdzielą jedno jądro systemu i jedną pulę pamięci. Pod, w którym występuje wyciek pamięci, nie zawsze kończy działanie w izolacji. Mechanizm OOM (out of memory) killer wybiera ofiarę na podstawie wyniku ważonego pod kątem dużych procesów, a k3s jest dużym, długo działającym procesem. W rezultacie zamiast poda może zostać zamknięty cały klaster. W takiej sytuacji nic nie zrestartuje obciążeń, ponieważ komponent odpowiedzialny za ich restart przestał działać.
Obiekt LimitRange uzupełnia limity dla podów, które ich nie posiadają:
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: default
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 50m
memory: 128MiObiekt ResourceQuota ogranicza zasoby, które może zająć cała przestrzeń nazw (namespace):
apiVersion: v1
kind: ResourceQuota
metadata:
name: cap
namespace: default
spec:
hard:
limits.cpu: "3"
limits.memory: 3Gi
pods: "20"Następnie należy zarezerwować miejsce dla samego k3s w /etc/rancher/k3s/config.yaml:
kubelet-arg:
- 'system-reserved=cpu=250m,memory=512Mi'Różnicę widać w dwóch miejscach. Kontener zamknięty z powodu przekroczenia własnego limitu zgłasza Reason: OOMKilled w sekcji Last State w poleceniu kubectl describe pod, a reszta węzła działa dalej. Węzeł, w którym całkowicie zabrakło pamięci, pozostawia wpis Killed process w dmesg i zazwyczaj powoduje awarię sąsiednich procesów. Pierwszy przypadek oznacza, że limit spełnia swoje zadanie. Drugi przypadek to sytuacja, której limity mają zapobiegać.
Skanowanie manifestów w CI oraz w klastrze k3s według harmonogramu
Skanowanie powinno odbywać się w dwóch miejscach, ponieważ pozwala wykryć różne typy problemów. Najpierw zainstaluj Trivy. Projekt udostępnia pakiet Debian przy każdym wydaniu. W sierpniu 2026 roku aktualną wersją była 0.74.0, dlatego przed przypięciem jej do automatyzacji sprawdź stronę z wydaniami w poszukiwaniu nowszej wersji.
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 --versionPierwszym miejscem są manifesty, zanim trafią do klastra.
trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deploy--exit-code 1 przerywa zadanie CI (ciągłej integracji) w przypadku znalezienia zagrożenia. Każdy wynik wskazuje pole, które nie przeszło weryfikacji, oraz jego poziom ważności. Dzięki temu privileged: true, którego nie zamierzałeś zatwierdzić, przerywa budowanie zamiast trafić do API server. Znalezisko, które zdecydowałeś się zaakceptować, umieszcza się w pliku .trivyignore, co pozwala zachować tę decyzję w git obok manifestu, który wywołał alert.
Drugim miejscem jest działający klaster, skanowany według harmonogramu.
trivy k8s --compliance=k8s-cis-1.23 --report summaryUwaga dotycząca uczciwości w odniesieniu do tego polecenia. trivy k8s wdraża pod zbierający dane z węzła, który wymaga dostępu do hosta, aby sprawdzić ustawienia na poziomie węzła. W klastrze, w którym właśnie zacząłeś odrzucać uprzywilejowane pody, warto zwrócić na to uwagę, zamiast szukać obejść. trivy k8s --report summary --disable-node-collector pomija zbieranie danych, tracąc przy tym kontrole na poziomie węzła.
kube-bench obejmuje stronę hosta: uprawnienia do plików i flagi procesów, gdzie w rzeczywistości znajduje się większość benchmarków CIS (Center for Internet Security). Narzędzie zawiera profil dla k3s. Pobierz manifest Job z oficjalnego źródła i dostosuj go.
curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yamlZmień polecenie kontenera na ["kube-bench", "--benchmark", "k3s-cis-1.7"] oraz zamień montowania /etc/kubernetes i /var/lib/etcd na /etc/rancher i /var/lib/rancher, ponieważ to tam k3s przechowuje swoje pliki. Następnie:
kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1Zwróć uwagę, czym jest ten Job: hostPID: true z zamontowanymi katalogami hosta, co jest dokładnie taką samą strukturą poda, której odrzucanie zacząłeś w poprzedniej sekcji. Uruchom go w wykluczonym namespace kube-system, odczytaj wynik, a następnie kubectl delete job kube-bench. Skaner, który musi być uprzywilejowany, nie jest powodem, by przestać blokować uprzywilejowane obciążenia.
Zawartość obrazów to osobna kwestia. trivy image ghcr.io/example/app:1.4 odczytuje bazę danych pakietów wewnątrz obrazu i wymienia znane podatności. Jest to to samo zadanie, co w przypadku sprawdzania serwera pod kątem znanych CVE.
Teraz szczera uwaga na temat wyników skanera. Jednowęzłowy klaster hobbystyczny nie przejdzie długiej listy kontroli CIS, a większość tych niepowodzeń jest jednocześnie poprawna i nieistotna dla Twojego przypadku. Benchmark jest napisany dla klastra wielowęzłowego i wielodzierżawnego: etcd na osobnych hostach, logi audytowe wysyłane poza maszynę, osobny urząd certyfikacji dla kubelet, wtyczki admission dla reżimu zgodności, któremu nie podlegasz. k3s celowo uruchamia control plane jako pojedynczy proces z jednym plikiem konfiguracyjnym, więc kontrole sprawdzające uprawnienia pliku manifestu kube-scheduler nie mogą przejść, ponieważ taki plik nie istnieje.
Czytaj niepowodzenia w tej kolejności i zatrzymaj się, gdy przestaną wnosić wartość: tryby plików i właściciel w /etc/rancher i /var/lib/rancher, wszystko co wspomina o dostępie anonimowym lub nieuwierzytelnionym, wszystko co zgłasza komponent powiązany z 0.0.0.0 oraz każdy kontener działający jako UID 0 bez uzasadnionego powodu. Reszta może poczekać na drugi węzeł lub drugą osobę z dostępem. Raport na sto linii, który ignorujesz, jest mniej wart niż pięcioliniowy, na podstawie którego podejmujesz działania.
Kolejność utwardzania k3s dla pojedynczego węzła
- Ustaw domyślną politykę przychodzącą ufw na deny, zezwól na SSH, zezwól na dostęp do 6443 z własnego adresu oraz zezwól na ruch w sieciach pod i service.
- Skopiuj kubeconfig do użytkownika z uprawnieniami 600 i nigdy nie ustawiaj
--write-kubeconfig-mode 644. - Potwierdź, że kubelet na porcie 10250 odrzuca anonimowe żądania i nie wystawiaj tego portu do Internetu.
- Wyłącz nieużywane komponenty wbudowane, a następnie zrestartuj k3s.
- Oznacz przestrzenie nazw (namespaces) dla Pod Security admission za pomocą
enforce=baselineorazwarn=restricted. - Wyłącz domyślne automatyczne montowanie tokenów ServiceAccount.
- Dodaj LimitRange oraz ResourceQuota i zarezerwuj zasoby CPU oraz pamięci dla k3s.
- Umieść
trivy fs --scanners misconfigw CI i przeprowadzaj skanowanie CIS co miesiąc.
Host, na którym działa klaster, wymaga takiej samej dbałości jak każdy inny serwer, a k3s wprowadza tu dodatkowy aspekt. Oprogramowanie zostało zainstalowane za pomocą skryptu, a nie przez apt, więc apt upgrade nigdy go nie aktualizuje. Utrzymuj system operacyjny w stanie załatanym zgodnie z własnym harmonogramem, korzystając z unattended upgrades on Ubuntu, a k3s aktualizuj w sposób kontrolowany, uruchamiając ponownie instalator z wybranym kanałem lub wersją. Łatwo zapomnieć o dwóch ścieżkach aktualizacji na jednej maszynie, dlatego należy odnotować, który komponent podlega której metodzie.
FAQ
Czy wystawianie serwera API k3s na porcie 6443 do Internetu jest bezpieczne?
Samo w sobie nie stanowi otwartej bramy, ponieważ serwer API wymaga certyfikatu klienta lub tokena i odrzuca wszelkie inne próby połączenia z kodem forbidden: User "system:anonymous". Istnieją jednak dwa zagrożenia. Anonimowi użytkownicy mogą odczytać /version, co pozwala skanerom na precyzyjne określenie wersji Kubernetes. Ponadto każda przyszła luka w serwerze API staje się osiągalna zdalnie, gdy port jest otwarty. W klastrze jednowęzłowym żaden zewnętrzny zasób nie potrzebuje dostępu do portu 6443 poza własnym kubectl, dlatego należy zezwolić na ruch tylko z określonego adresu za pomocą sudo ufw allow from YOUR_IP to any port 6443 proto tcp i pozwolić domyślnej polityce odmowy na obsługę reszty.
Dlaczego reguła ufw nie blokuje usługi NodePort?
Ponieważ pakiet nigdy nie dociera do łańcucha, w którym znajduje się reguła. kube-proxy umieszcza reguły DNAT w łańcuchu PREROUTING tabeli nat, który jest przetwarzany jako pierwszy i zmienia adres docelowy na adres poda. Pakiet jest następnie przekazywany (forwarded), a nie dostarczany lokalnie, więc przechodzi przez łańcuch FORWARD, omijając INPUT, gdzie znajdują się reguły ufw. Można to potwierdzić za pomocą sudo iptables -S PREROUTING -t nat | head. Filtruj NodePorty na poziomie sieciowej zapory ogniowej dostawcy lub unikaj type: NodePort i uzyskuj dostęp do usług za pomocą kubectl port-forward.
Czy należy uruchamiać k3s z --write-kubeconfig-mode 644?
Nie. Dokumentacja k3s wyjaśnia skutki tego działania: „Zmiana trybu na 644 pozwoli na odczyt pliku przez innych nieuprzywilejowanych użytkowników w systemie”. Plik ten zawiera certyfikat klienta dla system:masters, więc tryb 644 czyni każde lokalne konto administratorem klastra. Zamiast tego należy skopiować plik dla wybranego użytkownika za pomocą sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config, pozostawiając oryginał w 600 root:root.
Który benchmark kube-bench należy stosować dla k3s?
Należy użyć k3s-cis-1.7. Dokumentacja kube-bench wskazuje: „kube-bench zawiera benchmarki dla platformy Rancher K3S. Aby je uruchomić, należy określić --benchmark k3s-cis-1.7 podczas wywoływania polecenia kube-bench”. Należy przekazać tę flagę jawnie, ponieważ automatyczne wykrywanie zakłada układ plików kubeadm, podczas gdy k3s przechowuje pliki w /etc/rancher oraz /var/lib/rancher. Należy spodziewać się niepowodzeń w testach, które nie dotyczą pojedynczego węzła, i w pierwszej kolejności zająć się uprawnieniami plików oraz dostępem anonimowym.
Czy mechanizm Pod Security admission spowoduje awarię wbudowanych komponentów k3s?
Tak, jeśli zostanie wymuszony w kube-system. Pody ServiceLB, które k3s tworzy dla usług type: LoadBalancer, zajmują porty na hoście, a baseline zabrania używania portów hosta, więc pody te zostaną odrzucone przy następnej próbie odtworzenia. Należy wykluczyć kube-system w pliku konfiguracyjnym admission lub stosować Pod Security wyłącznie jako etykiety w przestrzeniach nazw, którymi zarządzasz. Zacznij od enforce=baseline oraz warn=restricted, aby sprawdzić, co restricted mogłoby zepsuć przed włączeniem tej funkcji.