SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

Cách harden k3s một node trên VPS public

Bản cài k3s mặc định mở TCP 6443, kubelet 10250, NodePort và pod đặc quyền. Làm theo các bước để khóa API, kubeconfig và firewall đúng cách.

Những thành phần k3s cluster đơn node phơi bày ngay từ đầu

Một k3s cluster đơn node trên VPS công cộng sẽ bị phơi bày tại 5 vị trí cụ thể ngay sau khi trình cài đặt một dòng lệnh hoàn tất: Kubernetes API server trên TCP 6443, kubelet trên TCP 10250, file kubeconfig nằm trên ổ đĩa, dải NodePort mà firewall của bạn không thấy, và bất kỳ pod nào được phép yêu cầu privileged hoặc hostPath. Mỗi vị trí đều có cách khắc phục chỉ mất vài phút. Hướng dẫn này giả định k3s đã chạy, vì vậy nếu chưa, hãy bắt đầu với cài đặt k3s đơn node trên VPS rồi quay lại sau.

Hãy xem những gì đang lắng nghe trước khi bạn thay đổi bất cứ điều gì.

sudo ss -tulpn | grep -E '6443|10250|10256|8472'

Cài đặt mặc định cho thấy 6443 (API server), 10250 (kubelet) và 8472/udp (flannel overlay, sử dụng VXLAN - virtual extensible LAN) đang lắng nghe trên mọi địa chỉ, vì vậy mỗi thành phần này đều nằm trên địa chỉ công cộng của bạn chứ không chỉ trên loopback. 10256 (kube-proxy health check) cũng xuất hiện, nhưng k3s bind nó vào 127.0.0.1 (::1 trên node IPv6), nên một lần quét từ máy khác sẽ không thấy nó.

Vì sao port 6443 kiểm soát toàn bộ cluster

Bất kỳ ai có thể xác thực vào port 6443 với quyền admin đều có thể tạo pod, và pod có thể chạy với quyền root trên host. Port 6443 là cổng vào máy chủ.

Mở port 6443 không đồng nghĩa máy chủ sẽ bị xâm nhập ngay, vì Kubernetes không chấp nhận password. Nó yêu cầu client certificate hoặc bearer token. Tuy vậy, vẫn có 2 điều cần lưu ý.

Thứ nhất, API server phản hồi một số request mà không cần credential nào. Kubernetes RBAC (role-based access control) mặc định bind group system:unauthenticated vào role có tên system:public-info-viewer. Role này cho phép /version, /healthz, /livez và /readyz. Từ một máy khác:

curl -sk https://YOUR_SERVER_IP:6443/version

Lệnh này trả về chính xác version Kubernetes của bạn. Đây là dữ liệu đầu vào cho việc tìm kiếm CVE (common vulnerabilities and exposures), đồng thời là lý do scanner đánh giá máy chủ của bạn đáng chú ý. Các path ngoài những path này đều bị từ chối, và phản hồi từ chối sẽ nêu rõ danh tính của bạn:

forbidden: User "system:anonymous" cannot get path "/api"

Thứ hai, mọi lỗi trong API server đều có thể bị truy cập từ xa khi port này đang mở. Khi đó, việc patch không còn là tùy chọn.

Cách sửa đơn giản là thêm rule vào firewall. Trong trường hợp này, ufw cần nhiều hơn một dòng vì k3s định tuyến traffic của cluster qua cùng kernel.

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 enable

2 rule cuối lấy trực tiếp từ tài liệu k3s. 10.42.0.0/16 là pod network mặc định và 10.43.0.0/16 là service network mặc định. Nếu thiếu 2 rule này, ufw sẽ drop traffic nội bộ của cluster, khiến các pod mất kết nối đến API server và lẫn nhau. Ví dụ của k3s cho phép 6443 từ mọi nơi; thay giá trị đó bằng địa chỉ của bạn là thay đổi đáng thực hiện. Nếu bạn chưa quen với ufw, phần cơ bản về ufw firewall cho VPS giải thích các policy mặc định mà cấu hình này phụ thuộc vào.

Tài liệu k3s nói rất rõ về overlay port: "VXLAN port trên các node không được expose ra Internet vì điều đó cho phép bất kỳ ai truy cập vào cluster network của bạn." Policy mặc định deny đối với incoming traffic sẽ xử lý việc này mà không cần chỉ rõ port.

Cách sửa chắc chắn hơn là ngừng truy cập API qua public address và sử dụng địa chỉ VPN hoặc mesh thay thế. Server certificate phải liệt kê địa chỉ mà bạn kết nối đến, vì vậy hãy thêm địa chỉ đó dưới dạng SAN (subject alternative name) trong /etc/rancher/k3s/config.yaml:

tls-san:
  - 10.8.0.1
  - k3s.example.com
secrets-encryption: true
sudo systemctl restart k3s
sudo k3s secrets-encrypt status

secrets-encryption: true mã hóa các object Secret trong datastore. Tài liệu k3s lưu ý rằng "Không thể bật mã hóa Secret trên một server hiện có nếu không restart server", và các Secret được ghi trước khi thay đổi vẫn giữ dạng cũ cho đến khi bạn chạy sudo k3s secrets-encrypt reencrypt. Cần hiểu rõ lợi ích của việc này. Nó bảo vệ file datastore được copy từ một bản backup. Nó không ngăn được người có thể giao tiếp với API server, vì API server sẽ giải mã Secret cho bất kỳ ai được phép đọc chúng. Phân biệt này cũng áp dụng cho mọi secret store tự host, đó là lý do bài hardening Vaultwarden tập trung vào admin token và file backup thay vì bản thân cơ chế mã hóa.

Vì sao kubelet trên cổng 10250 đáng quan tâm

kubelet là agent khởi động các container. API của nó trên cổng 10250 liệt kê các pod và chạy lệnh bên trong chúng. kubelet chấp nhận request ẩn danh đồng nghĩa với việc mở một remote shell vào mọi workload trên máy.

Kiểm tra kubelet của bạn:

curl -sk https://127.0.0.1:10250/pods | head -c 60

k3s hiện tại trả về Unauthorized, vì kubelet yêu cầu API server xác thực và cấp quyền cho mọi caller. Nếu nhận được danh sách pod dạng JSON, quyền truy cập ẩn danh đang bật. Khi đó, bất kỳ ai truy cập được cổng 10250 đều có thể đọc dữ liệu và thực thi lệnh trong các container của bạn.

Dù kết quả thế nào, hãy chặn cổng này từ bên ngoài. Trên một node đơn, client duy nhất của kubelet là control plane trên cùng máy. Lưu lượng đó đi qua loopback interface, vốn được ufw cho phép mặc định. Chặn 10250 từ Internet không gây ảnh hưởng gì. Nếu bạn đến đây sau khi gặp kubectl top bị lỗi hoặc metrics-server không ổn định, nguyên nhân được tổng hợp trong lỗi kubelet trên cổng 10250.

kubeconfig của bạn là credential quản trị cluster

k3s tạo /etc/rancher/k3s/k3s.yaml với owner là root và mode 600. Tài liệu nêu rõ hậu quả khi thay đổi mode này: "File kubeconfig thuộc về root và được ghi với mode mặc định là 600. Đổi mode thành 644 sẽ cho phép những user không có quyền trên host đọc file này."

Hãy hiểu như sau: mode 644 khiến mọi account cục bộ trở thành cluster administrator. Nhiều hướng dẫn đề xuất đúng cách đó, thường dùng --write-kubeconfig-mode 644, để kubectl hoạt động mà không cần sudo. Cách này hoạt động vì nó cấp credential quản trị cho bất kỳ ai có shell.

Thay vào đó, hãy copy file cho một user cụ thể.

mkdir -p ~/.kube
sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config
kubectl get nodes

Sau đó xác nhận file gốc vẫn được bảo vệ chặt:

stat -c '%a %U:%G' /etc/rancher/k3s/k3s.yaml

600 root:root là kết quả bạn cần. File đó chứa client certificate của một thành viên trong system:masters, group mà API server mặc định cho phép không điều kiện, nên RBAC rules không bao giờ được áp dụng cho certificate này. Kubernetes không có certificate revocation list. Vì vậy, bản copy bị lộ vẫn hợp lệ cho đến khi bạn rotate certificate authority của cluster. Hãy bảo vệ file này như SSH private key và giữ số account có thể truy cập nó ở mức thấp, tương tự nguyên tắc user account có quyền tối thiểu trên VPS.

Firewall của bạn không thấy traffic NodePort

Một service type: NodePort mở một port trong khoảng từ 30000 đến 32767 trên mọi địa chỉ mà node đang có, bao gồm cả địa chỉ public. Một service type: LoadBalancer còn đi xa hơn trên k3s: ServiceLB, load balancer tích hợp sẵn, tạo một pod nhỏ cho mỗi service trong kube-system. Pod này chiếm trực tiếp service port trên host.

kubectl -n kube-system get pods | grep svclb
sudo ss -tulpn | grep -E ':3[0-2][0-9]{3}'

Đây là phần thường khiến mọi người bất ngờ. Bạn chặn port đó bằng ufw nhưng port vẫn tiếp tục trả lời.

sudo ufw deny 30080/tcp
curl http://YOUR_SERVER_IP:30080

Trang vẫn tải được vì packet đi qua các chain nào. kube-proxy ghi các rule DNAT (destination network address translation) vào chain PREROUTING của table nat. PREROUTING chạy trước khi kernel đưa ra quyết định filter. Destination được đổi thành địa chỉ pod. Địa chỉ này không phải host, nên kernel đưa packet qua chain FORWARD và không bao giờ đi qua INPUT. Các rule của ufw nằm trong INPUT. Packet không bao giờ gặp các rule đó. Thứ tự jump có thể xem như sau:

sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | head

KUBE-SERVICES nằm ở đầu chain PREROUTING. Các jump của Kubernetes trong FORWARD cũng nằm trước các chain riêng của ufw. Đây là cùng cơ chế cho phép Docker publish port vượt qua ufw, và cách xử lý cũng giống nhau.

  • Filter tại network firewall của provider. Firewall này chạy trước máy chủ và không phụ thuộc vào cách kernel định tuyến packet.
  • Không dùng NodePort và LoadBalancer. Giữ các service tại ClusterIP rồi truy cập chúng bằng kubectl port-forward qua SSH session hiện có.
  • Chỉ expose một ingress trên 80 và 443, không expose thêm gì khác.
  • Thu hẹp khoảng port bằng service-node-port-range trong kube-apiserver-arg, để một NodePort vô tình được tạo sẽ rơi vào khoảng bạn đang theo dõi.

ufw vẫn đáng dùng. Nó quản lý traffic gửi đến chính host, gồm SSH và API server. Nhưng ufw không kiểm soát traffic đến pod. Nếu kỳ vọng nó làm việc đó, cuối cùng database có thể bị expose ra ngoài.

Pod có hostPath hoặc privileged có quyền root trên VPS

Container chỉ là các process thông thường chạy trên kernel của bạn, với quyền xem kernel bị giới hạn. Một số field của pod có thể gỡ bỏ giới hạn đó.

  • securityContext.privileged: true cấp cho container mọi Linux capability và quyền truy cập vào device của host.
  • hostPath mount một thư mục của host vào pod. Pod mount / ở chế độ read-write có thể thêm một key vào /root/.ssh/authorized_keys.
  • hostPID: true đưa container vào process namespace của host, nơi nsenter trên PID 1 sẽ mở một shell trên host.
  • hostNetwork: true đưa container vào network stack của host. Khi đó, container có thể bind vào các port của host và truy cập các service đang bind vào loopback.

Vì vậy, câu hỏi “ai có thể tạo pod ở đây” cũng chính là câu hỏi “ai có quyền root trên VPS này”. Bất kỳ ServiceAccount nào có create trên pod trong bất kỳ namespace nào đều tương đương root, trừ khi có cơ chế từ chối pod trước đó.

Cơ chế đó là Pod Security admission, được tích hợp trong API server. Cách cấu hình nhanh là đặt một label cho từng namespace và không cần restart.

kubectl label namespace default \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/warn=restricted

baseline từ chối cả 4 field trên. restricted còn yêu cầu user không phải root, profile seccomp (secure computing mode), không được privilege escalation và phải drop capability xuống ALL. Điều này làm nhiều chart được công bố không hoạt động. Enforce baseline nhưng chỉ cảnh báo về restricted cho phép bạn xem trước những gì sẽ bị lỗi trước khi áp dụng chính thức.

Kiểm tra cơ chế này:

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: pstest
spec:
  containers:
  - name: app
    image: busybox
    command: ["sleep", "60"]
    securityContext:
      privileged: true
EOF

API server sẽ từ chối pod và cho biết field nào bị từ chối:

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)

Nếu muốn đặt mặc định cho toàn cluster thay vì gắn label cho từng namespace, k3s có tài liệu về file cấu hình admission tại /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]

Trỏ API server đến file này trong /etc/rancher/k3s/config.yaml, rồi restart k3s:

kube-apiserver-arg:
  - 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'

Exemption kube-system là bắt buộc. Các pod ServiceLB của k3s chiếm các port của host, điều mà baseline cấm. Nếu bỏ kube-system khỏi danh sách, các pod đó sẽ bị từ chối vào lần tiếp theo có thành phần nào tạo lại chúng. Hãy giữ một phiên SSH thứ hai khi restart k3s sau khi thay đổi admission.

Một điểm đáng chú ý khác: k3s tích hợp network policy controller và bật sẵn theo mặc định. Vì vậy, các object NetworkPolicy có hiệu lực trên cluster này mà không cần cài thêm gì. Điều này không đúng với mọi Kubernetes distribution. Đây là công cụ để ngăn một pod đã bị breach truy cập đến phần còn lại của hệ thống.

Gỡ các component đi kèm mà bạn không sử dụng

Installer triển khai một nhóm add-on. Mỗi add-on mở thêm một listener và tạo thêm một thành phần cần cập nhật bản vá. --disable chấp nhận các giá trị sau: coredns, servicelb, traefik, local-storage, metrics-server, runtimes.

Giữ lại coredns. Không có nó, không thành phần nào trong cluster phân giải được tên. Các thành phần còn lại là tùy chọn. Trong /etc/rancher/k3s/config.yaml:

disable:
  - traefik
  - servicelb
disable-helm-controller: true
sudo systemctl restart k3s
kubectl get pods -A

k3s xóa các component bạn disable, vì vậy các pod traefik và pod svclb- sẽ tự biến mất. Hãy hiểu rõ hệ quả trước khi thực hiện. Khi servicelb bị gỡ, mọi service type: LoadBalancer sẽ giữ nguyên ở <pending> vĩnh viễn vì không có thành phần nào gán địa chỉ cho chúng. Khi traefik bị gỡ, không còn ingress controller, nên các object Ingress hoàn toàn không có tác dụng. Hãy disable chúng khi bạn chuyển traffic bằng cách khác, chẳng hạn dùng reverse proxy trên host. Nếu đang sử dụng chúng thì giữ nguyên. disable-helm-controller: true gỡ controller theo dõi các resource HelmChart. Đây là một component có quyền cao mà bạn không cần nếu tự chạy helm.

Dừng tự động mount token ServiceAccount mặc định

Mọi pod đều nhận token ServiceAccount tại /var/run/secrets/kubernetes.io/serviceaccount/token, trừ khi bạn chỉ định khác. ServiceAccount default không có quyền RBAC, nên bản thân token này không làm được nhiều việc. Nhưng với kẻ tấn công bên trong một container đã bị xâm nhập, nó cung cấp một credential hợp lệ và một API server có thể truy cập được. Đây là bước đầu tiên trong hầu hết các hướng dẫn leo thang quyền trong cluster.

Hướng dẫn hardening của k3s tắt tính năng này theo từng 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}'

Kiểm tra xem thay đổi đã có hiệu lực chưa. Trước tiên, chờ pod khởi động vài giây.

kubectl run t --image=busybox --restart=Never --command -- sleep 30
kubectl exec t -- ls /var/run/secrets/kubernetes.io/serviceaccount

Path này đã biến mất, nên ls in ra No such file or directory. Workload thực sự cần truy cập API sẽ đặt automountServiceAccountToken: true trong pod spec của chính nó, nên không có gì bị khóa vĩnh viễn. Đây là một cải thiện nhỏ nếu xét riêng. Cải thiện lớn hơn là không cấp cho workload một ServiceAccount có quyền thực tế. Bạn có thể kiểm tra token hiện tại có thể làm được gì:

kubectl auth can-i --list --as=system:serviceaccount:default:default

Giới hạn tài nguyên để một pod không thể làm sập cả cluster

Trên một node duy nhất, control plane và workload của bạn dùng chung một kernel và một pool bộ nhớ. Một pod bị rò rỉ bộ nhớ không phải lúc nào cũng chỉ tự nó bị dừng. OOM (out of memory) killer của kernel chọn tiến trình để dừng dựa trên score, trong đó các tiến trình lớn có trọng số cao hơn. k3s là một tiến trình lớn chạy lâu dài, nên thứ biến mất có thể là cả cluster thay vì pod. Khi đó không còn thành phần nào khởi động lại workload, vì chính thành phần có nhiệm vụ khởi động lại workload đã bị dừng.

LimitRange tự điền limits cho các pod không thiết lập limits:

apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: default
spec:
  limits:
  - type: Container
    default:
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 50m
      memory: 128Mi

ResourceQuota giới hạn tổng tài nguyên mà toàn bộ namespace có thể yêu cầu:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cap
  namespace: default
spec:
  hard:
    limits.cpu: "3"
    limits.memory: 3Gi
    pods: "20"

Sau đó dành riêng tài nguyên cho chính k3s trong /etc/rancher/k3s/config.yaml:

kubelet-arg:
  - 'system-reserved=cpu=250m,memory=512Mi'

Sự khác biệt thể hiện ở 2 vị trí. Container bị kill vì vượt limit của chính nó sẽ báo Reason: OOMKilled trong Last State thuộc kubectl describe pod, còn các phần khác của node vẫn tiếp tục hoạt động. Node bị hết hoàn toàn bộ nhớ sẽ để lại một dòng Killed process trong dmesg và thường kéo theo các node lân cận. Trường hợp đầu là limit đang hoạt động đúng. Trường hợp thứ hai là lý do cần có các limit này.

Quét các manifest trong CI và k3s cluster theo lịch trình

Việc quét cần thực hiện ở hai nơi, và chúng phát hiện các vấn đề khác nhau. Hãy cài đặt Trivy trước. Dự án phát hành gói Debian cùng với mỗi bản release, và 0.75.0 là bản hiện hành vào ngày 11 tháng 10 năm 2026, vì vậy hãy kiểm tra trang release để lấy phiên bản mới hơn trước khi cố định nó vào tự động hóa.

sudo apt-get install -y wget
wget -q https://github.com/aquasecurity/trivy/releases/download/v0.75.0/trivy_0.75.0_Linux-64bit.deb
sudo dpkg -i trivy_0.75.0_Linux-64bit.deb
trivy --version

Nơi đầu tiên là các manifest của bạn, trước khi chúng đến được cluster.

trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deploy

--exit-code 1 sẽ làm fail job CI (continuous integration) nếu có kết quả phát hiện. Mỗi kết quả đều nêu tên trường bị lỗi và mức độ nghiêm trọng, vì vậy một privileged: true mà bạn không cố ý commit sẽ làm fail bản build thay vì để nó đến được API server. Một kết quả phát hiện mà bạn quyết định chấp nhận sẽ được đưa vào file .trivyignore, giúp lưu giữ quyết định đó trong git bên cạnh manifest đã gây ra nó.

Nơi thứ hai là cluster đang chạy, theo lịch trình.

trivy k8s --compliance=k8s-cis-1.23 --report summary

Một lưu ý trung thực về lệnh đó. trivy k8s triển khai một node collector pod cần quyền truy cập host để kiểm tra các thiết lập ở cấp độ node. Trên một cluster nơi bạn vừa bắt đầu từ chối các pod đặc quyền (privileged), điều này đáng để lưu ý thay vì tìm cách lách luật. trivy k8s --report summary --disable-node-collector bỏ qua collector và đồng thời mất đi các kiểm tra ở cấp độ node.

kube-bench bao phủ phía host: quyền file và các flag tiến trình, đây là nơi mà hầu hết các benchmark của CIS (Center for Internet Security) thực sự tồn tại. Nó cung cấp sẵn một profile cho k3s. Hãy lấy manifest Job từ upstream và điều chỉnh nó.

curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml

Thay đổi lệnh container thành ["kube-bench", "--benchmark", "k3s-cis-1.7"], và hoán đổi các mount /etc/kubernetes và /var/lib/etcd thành /etc/rancher và /var/lib/rancher, vì đó là nơi k3s lưu trữ các file của nó. Sau đó:

kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1

Hãy chú ý Job đó là gì: hostPID: true cộng với các mount thư mục host, chính xác là hình dạng pod mà bạn đã bắt đầu từ chối ở phần trước. Chạy nó trong namespace kube-system đã được miễn trừ, đọc kết quả, sau đó kubectl delete job kube-bench. Một trình quét cần quyền đặc quyền không phải là lý do để ngừng cấm các workload đặc quyền.

Nội dung image là một câu hỏi riêng biệt. trivy image ghcr.io/example/app:1.4 đọc cơ sở dữ liệu gói bên trong một image và liệt kê những gì được biết là có lỗ hổng, cùng một công việc như khi bạn kiểm tra server của mình để tìm các CVE đã biết.

Bây giờ là phần trung thực về kết quả của trình quét. Một hobby cluster đơn node sẽ fail một danh sách dài các kiểm soát CIS, và hầu hết các lỗi đó đều đúng nhưng không liên quan đến bạn. Benchmark được viết cho một cluster đa node, đa tenant: etcd trên các host riêng biệt, audit log được chuyển ra khỏi máy, một certificate authority riêng cho kubelet, các admission plugin cho một chế độ tuân thủ mà bạn không thuộc đối tượng áp dụng. k3s cố tình chạy control plane như một tiến trình duy nhất với một file cấu hình, vì vậy các kiểm soát kiểm tra quyền của file manifest kube-scheduler không thể vượt qua, vì không có file nào như vậy tồn tại.

Hãy đọc các lỗi theo thứ tự này và dừng lại khi giá trị không còn: chế độ file và quyền sở hữu dưới /etc/rancher và /var/lib/rancher, bất kỳ điều gì đề cập đến truy cập ẩn danh hoặc không xác thực, bất kỳ điều gì báo cáo một thành phần bind vào 0.0.0.0, và bất kỳ container nào chạy dưới UID 0 mà không có lý do. Phần còn lại có thể đợi đến node thứ hai hoặc người thứ hai có quyền truy cập. Một báo cáo trăm dòng mà bạn bỏ qua thì không giá trị bằng một báo cáo năm dòng mà bạn thực hiện xử lý.

Thứ tự hardening k3s cho một node đơn

  1. Đặt policy mặc định cho traffic vào của ufw là deny, cho phép SSH, cho phép cổng 6443 từ địa chỉ của bạn, và cho phép các pod network và service network.
  2. Copy kubeconfig cho user của bạn với mode 600, và không bao giờ đặt --write-kubeconfig-mode 644.
  3. Xác nhận kubelet trên cổng 10250 từ chối các request anonymous, đồng thời không expose cổng này ra Internet.
  4. Tắt các bundled component không sử dụng, sau đó restart k3s.
  5. Gắn label cho các namespace để dùng Pod Security admission với enforce=baseline và warn=restricted.
  6. Tắt cơ chế tự động mount token của ServiceAccount theo mặc định.
  7. Thêm LimitRange và ResourceQuota, đồng thời reserve CPU và memory cho k3s.
  8. Đưa trivy fs --scanners misconfig vào CI và chạy CIS scan mỗi tháng.

Host bên dưới vẫn cần được bảo trì như mọi server khác, nhưng k3s có một điểm cần lưu ý. k3s được cài bằng script thay vì apt, nên apt upgrade không bao giờ cập nhật nó. Tự patch operating system theo lịch riêng bằng unattended upgrades trên Ubuntu, và chủ động upgrade k3s bằng cách chạy lại installer với channel hoặc version mong muốn. Hai đường cập nhật trên cùng một máy rất dễ bị bỏ quên, vì vậy hãy ghi rõ component nào dùng đường cập nhật nào.

FAQ

Có an toàn khi expose k3s API server trên port 6443 ra Internet không?

Bản thân việc này không phải là mở toang hệ thống, vì API server yêu cầu client certificate hoặc token và từ chối mọi yêu cầu khác bằng forbidden: User "system:anonymous". Tuy nhiên vẫn còn 2 rủi ro. Client ẩn danh vẫn có thể đọc /version. Thông tin này cho scanner biết chính xác Kubernetes release nào cần kiểm tra. Ngoài ra, mọi lỗ hổng trong API server được phát hiện sau này đều có thể bị truy cập từ xa khi port này còn mở. Với cluster một node, không có máy nào bên ngoài cần truy cập 6443 ngoài kubectl của bạn. Vì vậy, chỉ cho phép địa chỉ của bạn bằng sudo ufw allow from YOUR_IP to any port 6443 proto tcp và để policy deny mặc định xử lý phần còn lại.

Vì sao rule ufw của tôi không chặn được service NodePort?

Vì packet không đi qua chain chứa rule của bạn. kube-proxy đặt các rule DNAT trong table nat, tại chain PREROUTING. Chain này chạy trước và rewrite destination thành địa chỉ pod. Sau đó packet được forward thay vì delivery cục bộ. Vì vậy, nó đi qua FORWARD và bỏ qua INPUT, nơi các rule của ufw được đặt. Xác nhận bằng sudo iptables -S PREROUTING -t nat | head. Hãy filter NodePort tại network firewall của nhà cung cấp, hoặc không dùng type: NodePort và truy cập service bằng kubectl port-forward.

Có nên chạy k3s với --write-kubeconfig-mode 644 không?

Không. Tài liệu k3s nêu rõ tác dụng của tùy chọn này: "Changing the mode to 644 will allow it to be read by other unprivileged users on the host." File đó chứa client certificate cho system:masters. Vì vậy, mode 644 biến mọi local account thành cluster administrator. Thay vào đó, hãy copy file cho một user bằng sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config và giữ file gốc ở 600 root:root.

Nên dùng benchmark kube-bench nào cho k3s?

Dùng k3s-cis-1.7. Tài liệu kube-bench nêu: "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." Hãy truyền tùy chọn này một cách rõ ràng, vì auto-detection giả định layout của kubeadm, trong khi k3s lưu file tại /etc/rancher và /var/lib/rancher. Một số kiểm tra sẽ fail vì không áp dụng cho cluster một node. Trước tiên, hãy xử lý quyền file và anonymous access.

Pod Security admission có làm các component tích hợp sẵn của k3s bị lỗi không?

Có, nếu bạn enforce policy này trên kube-system. Các pod ServiceLB mà k3s tạo cho service type: LoadBalancer sẽ claim port trên host. baseline cấm host port, nên các pod đó sẽ bị reject khi được tạo lại. Hãy exempt kube-system trong file cấu hình admission, hoặc chỉ áp dụng Pod Security bằng label trên các namespace do bạn quản lý. Bắt đầu bằng enforce=baseline và warn=restricted để đọc xem restricted sẽ làm hỏng những gì trước khi bật policy.