SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-21

امن‌سازی کلاستر تک‌نود k3s روی VPS

با بستن پورت‌های 6443 و 10250، محدود کردن دسترسی kubeconfig و کنترل دسترسی پادهای privileged، امنیت کلاستر k3s خود را روی VPS افزایش دهید. راهنمای عملی برای رفع حفره‌های امنیتی پیش‌فرض.

آنچه یک کلاستر تک‌نود k3s در روز اول در معرض دید قرار می‌دهد

یک کلاستر تک‌نود k3s روی یک VPS عمومی، یک روز پس از اتمام نصب‌کننده تک‌خطی، در پنج نقطه مشخص در معرض دید قرار می‌گیرد: سرور API کوبرنتیز روی TCP 6443، kubelet روی TCP 10250، فایل kubeconfig که روی دیسک قرار دارد، محدوده NodePort که فایروال شما قادر به دیدن آن نیست، و هر پادی که اجازه درخواست برای privileged یا hostPath را داشته باشد. هر کدام از این موارد راه حلی دارند که تنها چند دقیقه زمان می‌برد. این راهنما فرض می‌کند که k3s از قبل در حال اجرا است؛ بنابراین اگر چنین نیست، با نصب تک‌نود k3s روی یک VPS شروع کنید و سپس به اینجا بازگردید.

پیش از هر تغییری، بررسی کنید چه چیزی در حال گوش دادن است.

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

یک نصب پیش‌فرض، پورت‌های 6443 (سرور API)، 10250 (kubelet)، 10256 (بررسی سلامت kube-proxy) و 8472/udp (لایه overlay flannel که از VXLAN یا همان شبکه مجازی توسعه‌پذیر استفاده می‌کند) را نشان می‌دهد. k3s به‌طور پیش‌فرض به 0.0.0.0 متصل می‌شود، بنابراین تمام این موارد نه تنها روی loopback، بلکه روی آدرس عمومی شما نیز قرار دارند.

چرا پورت 6443 کل کلاستر را در بر می‌گیرد

هر چیزی که بتواند با دسترسی مدیریتی به پورت 6443 احراز هویت کند، قادر به ایجاد pod است و یک pod می‌تواند روی میزبان (host) دسترسی root داشته باشد. پورت 6443 در واقع درب ورودی به ماشین است.

باز بودن پورت 6443 به معنای نفوذ فوری نیست، زیرا Kubernetes رمز عبور را نمی‌پذیرد. این سرویس به گواهی کلاینت یا bearer token نیاز دارد. با این حال، دو نکته همچنان صادق است.

نخست، API server به برخی درخواست‌ها بدون هیچ‌گونه اعتبارنامه‌ای پاسخ می‌دهد. در RBAC (کنترل دسترسی مبتنی بر نقش) پیش‌فرض Kubernetes، گروه system:unauthenticated به نقشی به نام system:public-info-viewer متصل است که اجازه دسترسی به /version، /healthz، /livez و /readyz را می‌دهد. از یک ماشین دیگر:

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

این دستور نسخه دقیق Kubernetes شما را برمی‌گرداند که ورودی اصلی برای جستجوی CVE (آسیب‌پذیری‌ها و مواجهه‌های رایج) است و دلیلی است که یک اسکنر تصمیم می‌گیرد ماشین شما هدف جالبی است. هر چیزی فراتر از این مسیرها رد می‌شود و پاسخ رد، هویت شما را مشخص می‌کند:

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

دوم، هر باگ در API server تا زمانی که این پورت باز باشد، از راه دور قابل بهره‌برداری است. در این نقطه، وصله‌کردن (patching) دیگر اختیاری نیست.

راهکار ارزان، استفاده از یک قانون فایروال است. ufw در اینجا به بیش از یک خط نیاز دارد، زیرا k3s ترافیک کلاستر را از طریق همان هسته (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

دو قانون آخر مستقیماً از مستندات k3s گرفته شده‌اند. 10.42.0.0/16 شبکه پیش‌فرض podها و 10.43.0.0/16 شبکه پیش‌فرض سرویس‌ها است؛ بدون این قوانین، ufw ترافیک داخلی کلاستر را مسدود می‌کند و در نتیجه podها دسترسی به API server و یکدیگر را از دست می‌دهند. مثال k3s اجازه دسترسی به پورت 6443 را از همه جا می‌دهد؛ جایگزین کردن آن با آدرس خودتان، تغییری است که ارزش انجام دادن دارد. اگر با ufw آشنا نیستید، اصول اولیه فایروال ufw برای VPS سیاست‌های پیش‌فرضی که این تنظیمات به آن وابسته است را پوشش می‌دهد.

مستندات k3s در مورد پورت overlay صریح هستند: "پورت VXLAN روی نودها نباید در معرض دید عموم قرار گیرد، زیرا شبکه کلاستر شما را برای دسترسی هر کسی باز می‌گذارد." یک سیاست پیش‌فرض deny برای ترافیک ورودی، بدون نیاز به ذکر نام پورت، این مشکل را حل می‌کند.

راهکار قوی‌تر این است که دسترسی به API از طریق آدرس عمومی را به‌طور کامل متوقف کنید و به جای آن از VPN یا آدرس mesh استفاده کنید. گواهی سرور باید آدرسی که به آن متصل می‌شوید را فهرست کند، بنابراین آن را به عنوان یک SAN (نام جایگزین موضوع) در /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 اشیاء Secret را در datastore رمزگذاری می‌کند. مستندات k3s اشاره می‌کنند که "رمزگذاری Secretها را نمی‌توان روی یک سرور موجود بدون راه‌اندازی مجدد فعال کرد" و Secretهایی که پیش از این تغییر نوشته شده‌اند، تا زمانی که sudo k3s secrets-encrypt reencrypt را اجرا نکنید، فرمت قدیمی خود را حفظ می‌کنند. درک کنید که این کار چه دستاوردی دارد. این کار از فایل datastore که از روی نسخه پشتیبان کپی شده باشد محافظت می‌کند. اما در برابر کسی که می‌تواند با API server صحبت کند، هیچ کاری انجام نمی‌دهد، زیرا API server برای هر کسی که اجازه خواندن Secretها را داشته باشد، آن‌ها را رمزگشایی می‌کند. همین تمایز در هر مخزن Secret خود-میزبانی (self-hosted) وجود دارد، و به همین دلیل است که یک مرحله مقاوم‌سازی برای Vaultwarden زمان خود را صرف توکن مدیریتی و فایل پشتیبان می‌کند، نه خودِ رمزگذاری.

اهمیت kubelet روی پورت 10250

kubelet عاملی است که کانتینرها را اجرا می‌کند. API آن روی پورت 10250، پادها را لیست کرده و دستورات را درون آن‌ها اجرا می‌کند. kubeletای که درخواست‌های ناشناس (anonymous) را می‌پذیرد، در واقع یک shell از راه دور به تمام workloadهای روی آن ماشین است.

وضعیت خود را بررسی کنید:

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

یک k3s به‌روز، پاسخ Unauthorized می‌دهد، زیرا kubelet از API server می‌خواهد که هر تماس‌گیرنده را احراز هویت و مجوزدهی کند. اگر به‌جای آن یک لیست JSON از پادها دریافت کردید، دسترسی ناشناس فعال است و هر کسی که به پورت 10250 دسترسی داشته باشد، می‌تواند از کانتینرهای شما اطلاعات بخواند یا در آن‌ها دستور اجرا کند.

در هر صورت، دسترسی به این پورت را از خارج ببندید. در یک تک‌نود (single node)، تنها کلاینت kubelet، کنترل پلین روی همان دستگاه است و آن ترافیک از طریق رابط loopback وارد می‌شود که ufw به‌صورت پیش‌فرض آن را می‌پذیرد. مسدود کردن پورت 10250 از سمت اینترنت هیچ هزینه‌ای برای شما ندارد. اگر از طریق یک kubectl top خراب یا یک metrics-server که پایدار نمی‌شود به اینجا رسیده‌اید، دلایل آن در خطاهای پورت 10250 kubelet جمع‌آوری شده است.

فایل kubeconfig شما یک اعتبارنامه مدیریت کلاستر است

نرم‌افزار k3s فایل /etc/rancher/k3s/k3s.yaml را با مالکیت root و سطح دسترسی 600 ایجاد می‌کند. مستندات رسمی در مورد تغییر این سطح دسترسی چنین هشدار می‌دهد: «فایل kubeconfig متعلق به کاربر root است و به‌صورت پیش‌فرض با سطح دسترسی 600 نوشته می‌شود. تغییر این سطح به 644 باعث می‌شود سایر کاربران بدون دسترسی ویژه (unprivileged) در میزبان نیز بتوانند آن را بخوانند.»

این جمله را این‌گونه تفسیر کنید: سطح دسترسی 644 باعث می‌شود هر حساب کاربری محلی به یک مدیر کلاستر تبدیل شود. بسیاری از راهنماها دقیقاً همین کار را پیشنهاد می‌دهند، معمولاً با دستور --write-kubeconfig-mode 644، تا دستور kubectl بدون نیاز به sudo اجرا شود. این روش با در اختیار قرار دادن اعتبارنامه مدیریت به هر کسی که به shell دسترسی دارد، کار می‌کند.

به‌جای این کار، فایل را فقط برای یک کاربر کپی کنید.

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

سپس تأیید کنید که فایل اصلی همچنان محدود باقی مانده است:

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

600 root:root همان پاسخی است که به دنبال آن هستید. آن فایل حاوی یک گواهی کلاینت برای عضوی از system:masters است؛ گروهی که API server آن را بدون قید و شرط مجاز می‌داند، بنابراین قوانین RBAC هرگز برای آن بررسی نمی‌شوند. کوبرنتیز هیچ لیست ابطال گواهی (CRL) ندارد، به این معنی که اگر نسخه‌ای از آن لو برود، تا زمانی که Certificate Authority کلاستر را چرخش (rotate) ندهید، همچنان معتبر باقی می‌ماند. با آن مانند یک کلید خصوصی SSH رفتار کنید و تعداد حساب‌هایی که به آن دسترسی دارند را محدود نگه دارید؛ این همان استدلالی است که در حساب‌های کاربری با حداقل دسترسی در VPS مطرح شد.

فایروال شما ترافیک NodePort را نمی‌بیند

یک سرویس type: NodePort پورتی بین 30000 و 32767 را روی تمام آدرس‌هایی که نود در اختیار دارد، از جمله آدرس عمومی، باز می‌کند. یک سرویس type: LoadBalancer در k3s فراتر می‌رود: ServiceLB، که لود بالانسر پیش‌فرض است، یک پاد کوچک برای هر سرویس در kube-system زمان‌بندی می‌کند که مستقیماً پورت سرویس را روی هاست اشغال می‌کند.

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

اکنون بخشی که باعث تعجب کاربران می‌شود: اگر آن پورت را با ufw مسدود کنید، همچنان پاسخ می‌دهد.

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

صفحه همچنان بارگذاری می‌شود، زیرا مسیر حرکت بسته متفاوت است. kube-proxy قوانین DNAT (ترجمه آدرس شبکه مقصد) را در زنجیره PREROUTING جدول nat می‌نویسد و PREROUTING پیش از هرگونه تصمیم فیلترینگ اجرا می‌شود. مقصد به آدرس پاد تغییر می‌کند که هاست نیست، بنابراین هسته بسته را به زنجیره FORWARD می‌فرستد و هرگز از INPUT عبور نمی‌کند. قوانین ufw در INPUT قرار دارند. بسته هرگز با آن‌ها مواجه نمی‌شود. ترتیب پرش‌ها قابل مشاهده است:

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

KUBE-SERVICES در بالای PREROUTING قرار دارد و پرش‌های Kubernetes در FORWARD بالاتر از زنجیره‌های اختصاصی ufw هستند. این همان مکانیزمی است که به Docker اجازه می‌دهد پورت‌ها را بدون توجه به ufw منتشر کند و پاسخ‌ها نیز همان است.

  • در فایروال شبکه ارائه‌دهنده خدمات خود فیلتر کنید. این فایروال پیش از ماشین قرار دارد و برای آن اهمیتی ندارد که هسته شما چگونه مسیریابی می‌کند.
  • از NodePort و LoadBalancer صرف‌نظر کنید. سرویس‌ها را روی ClusterIP نگه دارید و با استفاده از kubectl port-forward از طریق نشست SSH که از قبل دارید به آن‌ها دسترسی پیدا کنید.
  • فقط یک Ingress را روی پورت‌های 80 و 443 در معرض دید قرار دهید و هیچ چیز دیگری را باز نگذارید.
  • محدوده پورت‌ها را با service-node-port-range در بخش kube-apiserver-arg محدود کنید تا اگر به‌طور تصادفی یک NodePort ایجاد شد، در جایی باشد که آن را تحت نظارت دارید.

استفاده از ufw همچنان ارزشمند است. این ابزار ترافیکی را که مستقیماً به خود هاست ارسال می‌شود، یعنی SSH و API server، مدیریت می‌کند. ufw صرفاً ترافیک پادها را کنترل نمی‌کند و انتظار داشتن چنین عملکردی از آن، همان دلیلی است که باعث می‌شود یک دیتابیس در دسترس عموم قرار بگیرد.

یک Pod با hostPath یا دسترسی privileged، در واقع root روی VPS شماست

کانتینرها فرآیندهای معمولی روی هسته سیستم‌عامل شما هستند که دید محدودی نسبت به آن دارند. چندین فیلد در Pod این محدودیت را از بین می‌برند.

  • securityContext.privileged: true تمام قابلیت‌های لینوکس (Linux capabilities) و دسترسی به دستگاه‌های میزبان را به کانتینر می‌دهد.
  • hostPath یک دایرکتوری میزبان را درون Pod مانت می‌کند. Podای که / را با دسترسی خواندن و نوشتن مانت کند، می‌تواند یک کلید به /root/.ssh/authorized_keys اضافه کند.
  • hostPID: true کانتینر را در فضای نام فرآیندهای میزبان (host process namespace) قرار می‌دهد، جایی که nsenter روی PID 1، یک شل (shell) روی میزبان باز می‌کند.
  • hostNetwork: true کانتینر را روی پشته شبکه میزبان قرار می‌دهد، جایی که می‌تواند پورت‌های میزبان را اشغال کند و به سرویس‌های متصل به loopback دسترسی پیدا کند.

بنابراین، پاسخ به این پرسش که «چه کسی می‌تواند در اینجا Pod ایجاد کند» دقیقاً همان پاسخ به این پرسش است که «چه کسی روی این VPS دسترسی root دارد». هر ServiceAccount که اجازه create روی Podها در هر namespace را داشته باشد، معادل root است، مگر اینکه چیزی پیش از آن، Pod را رد کند.

آن «چیزی» که Pod را رد می‌کند، Pod Security admission است که در API server تعبیه شده است. نسخه سریع آن، استفاده از یک label برای هر namespace است و نیازی به 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 هر چهار فیلد بالا را رد می‌کند. restricted سخت‌گیرانه‌تر است و به کاربر غیر-root، پروفایل seccomp (حالت محاسبات امن)، عدم ارتقای سطح دسترسی (privilege escalation) و کاهش قابلیت‌ها به ALL نیاز دارد که باعث از کار افتادن بسیاری از chartهای منتشرشده می‌شود. اعمال سطح baseline در حالی که فقط برای restricted هشدار صادر می‌شود، به شما اجازه می‌دهد پیش از نهایی کردن تنظیمات، متوجه شوید چه چیزی از کار می‌افتد.

اثبات عملکرد:

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 آن را رد می‌کند و می‌گوید کدام فیلد باعث رد شدن شده است:

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)

برای داشتن یک تنظیم پیش‌فرض در سطح کل کلاستر به‌جای label زدن به تک‌تک namespaceها، k3s یک فایل پیکربندی admission را در مسیر /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]

در فایل /etc/rancher/k3s/config.yaml به API server آدرس این فایل را بدهید و سپس k3s را restart کنید:

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

استثنای kube-system اختیاری نیست. Podهای ServiceLB خودِ k3s پورت‌های میزبان را اشغال می‌کنند که baseline آن را ممنوع کرده است؛ بنابراین اگر kube-system را از لیست حذف کنید، این Podها در اولین باری که بازسازی شوند، رد خواهند شد. هنگام restart کردن k3s پس از تغییرات admission، حتماً یک نشست SSH دوم باز نگه دارید.

یک نکته اضافی: k3s به‌صورت پیش‌فرض دارای یک کنترل‌کننده network policy است و آن را فعال می‌کند، بنابراین اشیاء NetworkPolicy بدون نیاز به نصب هیچ ابزار اضافی، روی این کلاستر اعمال می‌شوند. این موضوع در مورد تمام توزیع‌های Kubernetes صادق نیست و این ابزار اصلی برای جلوگیری از دسترسی یک Pod آلوده به سایر بخش‌های شبکه است.

حذف مؤلفه‌های همراهی که از آن‌ها استفاده نمی‌کنید

نصب‌کننده مجموعه‌ای از افزونه‌ها را مستقر می‌کند. هر کدام از این‌ها یک listener و یک مورد دیگر برای وصله کردن اضافه می‌کنند. --disable این مقادیر را می‌پذیرد: coredns، servicelb، traefik، local-storage، metrics-server، runtimes.

coredns را نگه دارید. هیچ چیزی در کلاستر بدون آن نامی را resolve نمی‌کند. بقیه موارد انتخابی هستند. در /etc/rancher/k3s/config.yaml:

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

k3s مؤلفه‌هایی که غیرفعال می‌کنید را حذف می‌کند، بنابراین پادهای traefik و پادهای svclb- خودبه‌خود ناپدید می‌شوند. ابتدا از پیامدها آگاه باشید. با حذف servicelb، هر سرویس type: LoadBalancer برای همیشه در وضعیت <pending> باقی می‌ماند، زیرا هیچ چیزی به آن آدرس اختصاص نمی‌دهد. با حذف traefik، هیچ ingress controller وجود نخواهد داشت، بنابراین اشیاء Ingress هیچ کاری انجام نمی‌دهند. زمانی که ترافیک را از طریق دیگری مانند یک reverse proxy روی میزبان مدیریت می‌کنید، آن‌ها را غیرفعال کنید و زمانی که از آن‌ها استفاده می‌کنید، به حال خود رها کنید. disable-helm-controller: true کنترل‌کننده‌ای را حذف می‌کند که منابع HelmChart را مانیتور می‌کند؛ این یک مؤلفه دارای دسترسی ویژه است که اگر خودتان helm را اجرا می‌کنید، از آن استفاده نمی‌کنید.

غیرفعال کردن automount پیش‌فرض توکن ServiceAccount

هر Pod به‌صورت پیش‌فرض یک توکن ServiceAccount در مسیر /var/run/secrets/kubernetes.io/serviceaccount/token دریافت می‌کند، مگر اینکه خلاف آن را تعیین کنید. اکانت default هیچ‌گونه مجوز RBAC ندارد، بنابراین توکن به‌تنهایی کارایی چندانی ندارد. آنچه این توکن در اختیار مهاجم در یک کانتینرِ نفوذشده قرار می‌دهد، یک اعتبارنامه معتبر و دسترسی به API server است که گام نخست در اکثر گزارش‌های مربوط به افزایش سطح دسترسی (escalation) در کلاستر محسوب می‌شود.

راهنمای امن‌سازی k3s این قابلیت را در سطح هر 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}'

بررسی کنید که این تغییر اعمال شده باشد. ابتدا چند ثانیه به Pod فرصت دهید تا بالا بیاید.

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

مسیر حذف شده است، بنابراین ls خروجی No such file or directory را نمایش می‌دهد. ورک‌لودی که واقعاً به دسترسی API نیاز دارد، گزینه automountServiceAccountToken: true را در pod spec خود تنظیم می‌کند، بنابراین هیچ سرویسی به‌طور دائمی مسدود نمی‌شود. این یک پیروزی کوچک است. دستاورد بزرگ‌تر، عدم تخصیص یک ServiceAccount با مجوزهای واقعی به ورک‌لودها است؛ شما می‌توانید ارزش فعلی یک توکن را این‌گونه مشاهده کنید:

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

محدودیت‌های منابع، برای جلوگیری از مختل شدن کلاستر توسط یک پاد

در یک نود واحد، کنترل پلین و ورک‌لودهای شما از یک هسته (kernel) و یک استخر حافظه مشترک استفاده می‌کنند. پادی که دچار نشت حافظه (memory leak) می‌شود، همیشه به‌تنهایی از بین نمی‌رود. مکانیزم OOM (مخفف out of memory) در هسته، قربانی خود را بر اساس امتیازی انتخاب می‌کند که به فرآیندهای بزرگ وزن بیشتری می‌دهد؛ از آنجا که k3s یک فرآیند بزرگ و طولانی‌مدت است، ممکن است به‌جای پاد، کل کلاستر از بین برود. در این حالت، دیگر چیزی برای راه‌اندازی مجدد ورک‌لودهای شما وجود ندارد، زیرا آن چیزی که مسئول راه‌اندازی مجدد ورک‌لودهاست، خود از بین رفته است.

یک LimitRange برای پادهایی که هیچ محدودیتی تعیین نکرده‌اند، محدودیت‌هایی را اعمال می‌کند:

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

یک ResourceQuota سقف منابعی که کل namespace می‌تواند درخواست کند را تعیین می‌کند:

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

سپس در /etc/rancher/k3s/config.yaml فضایی را برای خود k3s رزرو کنید:

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

تفاوت این دو در دو مورد مشخص می‌شود. کانتینری که به دلیل عبور از محدودیت خود کشته شده باشد، در kubectl describe pod و در بخش Last State وضعیت Reason: OOMKilled را گزارش می‌دهد و بقیه نود به کار خود ادامه می‌دهد. نودی که حافظه آن به‌طور کامل تمام شده باشد، یک خط Killed process در dmesg باقی می‌گذارد و معمولاً سرویس‌های همسایه را نیز با خود از کار می‌اندازد. مورد اول نشان‌دهنده عملکرد صحیح محدودیت‌های شماست. مورد دوم همان چیزی است که محدودیت‌ها برای جلوگیری از آن ایجاد شده‌اند.

اسکن مانیفست‌ها در CI و کلاستر k3s طبق زمان‌بندی

اسکن کردن باید در دو نقطه انجام شود، چرا که هر کدام مشکلات متفاوتی را شناسایی می‌کنند. ابتدا Trivy را نصب کنید. این پروژه با هر release یک بسته Debian ارائه می‌دهد و نسخه 0.74.0 در آگوست 2026 نسخه جاری بود؛ بنابراین پیش از آنکه آن را در اتوماسیون خود ثابت (pin) کنید، صفحه releaseها را برای یافتن نسخه‌ای جدیدتر بررسی کنید.

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 --version

نقطه اول، مانیفست‌های شماست، پیش از آنکه به کلاستر برسند.

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

دستور --exit-code 1 در صورت یافتن مشکل، job مربوط به CI (یکپارچه‌سازی مداوم) را با خطا مواجه می‌کند. هر نتیجه، فیلد دارای مشکل و سطح شدت آن را مشخص می‌کند، بنابراین یک privileged: true که قصد commit کردن آن را نداشتید، به جای رسیدن به API server، باعث شکست build می‌شود. یافته‌ای که تصمیم به پذیرش آن گرفته‌اید در یک فایل .trivyignore قرار می‌گیرد که این تصمیم را در git، کنار مانیفستی که باعث آن شده است، حفظ می‌کند.

نقطه دوم، کلاستر در حال اجراست که طبق زمان‌بندی اسکن می‌شود.

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

یک نکته صادقانه درباره آن دستور: trivy k8s یک pod جمع‌آوری‌کننده (collector) روی نود مستقر می‌کند که برای بررسی تنظیمات سطح نود، به دسترسی host نیاز دارد. در کلاستری که به‌تازگی شروع به رد کردن podهای privileged کرده‌اید، این موضوع ارزش توجه دارد، نه دور زدن آن. دستور trivy k8s --report summary --disable-node-collector از collector صرف‌نظر می‌کند و به تبع آن، بررسی‌های سطح نود را از دست می‌دهد.

ابزار kube-bench سمت host را پوشش می‌دهد: مجوزهای فایل و flagهای پردازش، که بخش عمده‌ای از CIS (مرکز امنیت اینترنت) benchmark در واقع در همین‌جا نهفته است. این ابزار یک profile برای k3s ارائه می‌دهد. مانیفست Job بالادستی (upstream) را بگیرید و آن را تنظیم کنید.

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

دستور container را به ["kube-bench", "--benchmark", "k3s-cis-1.7"] تغییر دهید و mountهای /etc/kubernetes و /var/lib/etcd را با /etc/rancher و /var/lib/rancher جایگزین کنید، زیرا k3s فایل‌های خود را در این مسیرها نگه می‌دارد. سپس:

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

دقت کنید که آن Job چیست: hostPID: true به همراه mountهای دایرکتوری host، که دقیقاً همان شکل podای است که بخش قبل شروع به رد کردن آن کردید. آن را در namespace معاف‌شده kube-system اجرا کنید، خروجی را بخوانید و سپس kubectl delete job kube-bench. اسکنری که باید privileged باشد، دلیلی برای توقف ممنوعیت workloadهای privileged نیست.

محتوای imageها بحث جداگانه‌ای است. trivy image ghcr.io/example/app:1.4 دیتابیس بسته‌ها را در داخل یک image می‌خواند و موارد آسیب‌پذیر شناخته‌شده را لیست می‌کند؛ همان کاری که هنگام بررسی سرور خود برای CVEهای شناخته‌شده انجام می‌دهید.

و حالا بخش صادقانه درباره خروجی اسکنر: یک کلاستر hobby تک‌نوده، لیست بلندی از کنترل‌های CIS را با شکست مواجه می‌کند و اکثر این شکست‌ها هم درست هستند و هم برای شما بی‌اهمیت. این benchmark برای یک کلاستر چندنوده و چندمستاجری نوشته شده است: etcd روی hostهای جداگانه، ارسال لاگ‌های audit به خارج از ماشین، یک مرجع صدور گواهی (CA) جداگانه برای kubelet، و پلاگین‌های admission برای رژیم‌های انطباقی که شما مشمول آن‌ها نیستید. k3s عمداً control plane را به عنوان یک پردازش واحد با یک فایل پیکربندی اجرا می‌کند، بنابراین کنترل‌هایی که مجوزهای یک فایل مانیفست kube-scheduler را بررسی می‌کنند، نمی‌توانند پاس شوند، زیرا چنین فایلی وجود ندارد.

شکست‌ها را به این ترتیب بخوانید و هرجا که ارزش بررسی تمام شد، متوقف شوید: حالت فایل‌ها و مالکیت در مسیرهای /etc/rancher و /var/lib/rancher، هر چیزی که به دسترسی ناشناس یا احرازنشده اشاره دارد، هر گزارشی که مربوط به اتصال یک کامپوننت به 0.0.0.0 است، و هر containerای که بدون دلیل با UID 0 اجرا می‌شود. بقیه موارد می‌توانند تا زمان اضافه شدن نود دوم یا شخص دوم با دسترسی، منتظر بمانند. یک گزارش صد خطی که نادیده‌اش می‌گیرید، ارزش کمتری نسبت به یک گزارش پنج خطی دارد که بر اساس آن عمل می‌کنید.

ترتیب ایمن‌سازی k3s برای یک تک‌نود

  1. سیاست پیش‌فرض ورودی ufw را روی deny تنظیم کنید، SSH را مجاز کنید، پورت 6443 را فقط از آدرس خودتان باز بگذارید و شبکه‌های pod و service را مجاز کنید.
  2. فایل kubeconfig را با سطح دسترسی 600 به کاربر خود کپی کنید و هرگز --write-kubeconfig-mode 644 را تنظیم نکنید.
  3. اطمینان حاصل کنید که kubelet روی پورت 10250 درخواست‌های ناشناس را رد می‌کند و این پورت را از دسترس اینترنت خارج کنید.
  4. کامپوننت‌های همراه (bundled) که استفاده نمی‌کنید را غیرفعال کرده و سپس k3s را restart کنید.
  5. نیم‌اسپیس‌های خود را برای پذیرش Pod Security با enforce=baseline و warn=restricted برچسب‌گذاری کنید.
  6. قابلیت automount پیش‌فرض توکن‌های ServiceAccount را غیرفعال کنید.
  7. یک LimitRange و یک ResourceQuota اضافه کنید و مقداری CPU و حافظه برای k3s رزرو کنید.
  8. مقدار trivy fs --scanners misconfig را در CI قرار دهید و ماهانه یک اسکن CIS اجرا کنید.

میزبان زیرین همچنان به همان مراقبتی نیاز دارد که هر سرور دیگری به آن نیازمند است، و k3s پیچیدگی خاص خود را به آن می‌افزاید. این سرویس توسط یک اسکریپت نصب شده است نه توسط apt، بنابراین apt upgrade هرگز آن را مدیریت نمی‌کند. سیستم‌عامل را طبق برنامهٔ زمانی خود با unattended upgrades on Ubuntu به‌روز نگه دارید و k3s را به‌صورت دستی با اجرای مجدد نصب‌کننده با کانال یا نسخهٔ مورد نظر خود ارتقا دهید. فراموش کردن دو مسیر به‌روزرسانی روی یک ماشین آسان است، بنابراین یادداشت کنید که هر کامپوننت از چه مسیری پیروی می‌کند.

FAQ

آیا باز گذاشتن پورت 6443 برای API server در k3s روی اینترنت امن است؟

این کار به‌خودی‌خود یک درِ باز نیست، زیرا API server به گواهی کلاینت یا توکن نیاز دارد و سایر درخواست‌ها را با forbidden: User "system:anonymous" رد می‌کند. با این حال، دو ریسک باقی می‌ماند. تماس‌گیرندگان ناشناس همچنان می‌توانند /version را بخوانند که به اسکنرها دقیقاً می‌گوید کدام نسخه از Kubernetes را هدف قرار دهند. همچنین، هرگونه آسیب‌پذیری احتمالی در API server در آینده، تا زمانی که پورت باز باشد، از راه دور قابل‌دسترسی خواهد بود. در یک کلاستر تک‌نود، هیچ منبع خارجی به جز kubectl شما نیازی به پورت 6443 ندارد؛ بنابراین دسترسی را فقط برای آدرس خود با sudo ufw allow from YOUR_IP to any port 6443 proto tcp مجاز کنید و اجازه دهید سیاست پیش‌فرض deny بقیه موارد را مدیریت کند.

چرا قانون ufw من، سرویس‌های NodePort را مسدود نمی‌کند؟

زیرا بسته (packet) هرگز به زنجیره‌ای که قانون شما در آن قرار دارد نمی‌رسد. kube-proxy قوانین DNAT را در زنجیره PREROUTING جدول nat قرار می‌دهد که ابتدا اجرا شده و مقصد را به آدرس یک پاد تغییر می‌دهد. در نتیجه، بسته به جای تحویل محلی، Forward می‌شود و از زنجیره INPUT که قوانین ufw در آن قرار دارند، عبور نمی‌کند. این موضوع را با sudo iptables -S PREROUTING -t nat | head تأیید کنید. پورت‌های NodePort را در فایروال شبکه ارائه‌دهنده خود فیلتر کنید، یا از type: NodePort اجتناب کرده و به جای آن با kubectl port-forward به سرویس‌ها دسترسی پیدا کنید.

آیا باید k3s را با --write-kubeconfig-mode 644 اجرا کنم؟

خیر. مستندات k3s عملکرد آن را این‌گونه توضیح می‌دهد: «تغییر حالت به 644 باعث می‌شود فایل توسط سایر کاربران بدون دسترسی ویژه (unprivileged) در میزبان قابل خواندن باشد.» آن فایل حاوی گواهی کلاینت برای system:masters است، بنابراین حالت 644 باعث می‌شود هر اکانت محلی به یک مدیر کلاستر تبدیل شود. به جای این کار، فایل را با sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config فقط برای یک کاربر کپی کنید و فایل اصلی را در 600 root:root دست‌نخورده باقی بگذارید.

برای k3s از کدام بنچمارک kube-bench استفاده کنم؟

از k3s-cis-1.7 استفاده کنید. مستندات kube-bench بیان می‌کند: «kube-bench شامل بنچمارک‌هایی برای پلتفرم Rancher K3S است. برای اجرای آن، هنگام اجرای دستور kube-bench باید --benchmark k3s-cis-1.7 را مشخص کنید.» این پارامتر را صراحتاً ارسال کنید، زیرا تشخیص خودکار (auto-detection) فرض را بر ساختار kubeadm می‌گذارد، در حالی که k3s فایل‌های خود را در /etc/rancher و /var/lib/rancher نگه می‌دارد. انتظار خطاهایی را داشته باشید که برای یک نود تکی صدق نمی‌کنند و ابتدا روی مجوزهای فایل و دسترسی‌های ناشناس تمرکز کنید.

آیا Pod Security admission باعث اختلال در کامپوننت‌های پیش‌فرض k3s می‌شود؟

اگر آن را روی kube-system اعمال کنید، بله. پادهای ServiceLB که k3s برای سرویس‌های type: LoadBalancer ایجاد می‌کند، پورت‌هایی را روی میزبان اشغال می‌کنند و baseline استفاده از پورت‌های میزبان را ممنوع می‌کند؛ بنابراین این پادها در زمان بازسازی مجدد رد می‌شوند. kube-system را در فایل پیکربندی admission مستثنی کنید، یا Pod Security را فقط به صورت برچسب (label) روی نیم‌اسپیس‌هایی که خودتان ایجاد کرده‌اید اعمال کنید. با enforce=baseline و warn=restricted شروع کنید تا بتوانید پیش از فعال‌سازی نهایی، ببینید restricted چه چیزی را مسدود خواهد کرد.