VPS-এ k3s ক্লাস্টার সুরক্ষিত করার উপায়
একটি নতুন k3s ইনস্টলেশনে পোর্ট 6443, 10250, অনিরাপদ kubeconfig এবং উন্মুক্ত NodePort থাকে যা আপনার VPS-কে ঝুঁকিতে ফেলে। এই নির্দেশিকায় ধাপে ধাপে ক্লাস্টার হার্ডেনিংয়ের নিয়মগুলো দেখুন।
প্রথম দিন একটি single-node k3s cluster যা প্রকাশ করে
একটি public VPS-এ চালানো single-node k3s cluster-এ one-line installer শেষ হওয়ার পরের দিন পাঁচটি নির্দিষ্ট জায়গায় exposure থাকে: TCP 6443-এ Kubernetes API server, TCP 10250-এ kubelet, disk-এ থাকা kubeconfig file, firewall যে NodePort range দেখতে পায় না, এবং এমন যেকোনো pod যাকে privileged বা hostPath চাওয়ার অনুমতি দেওয়া হয়েছে। প্রতিটির সমাধান কয়েক মিনিটেই করা যায়। এই নির্দেশিকায় ধরে নেওয়া হয়েছে যে k3s ইতিমধ্যে চলছে। না চললে প্রথমে VPS-এ single-node k3s install করুন এবং পরে এখানে ফিরে আসুন।
কিছু পরিবর্তন করার আগে কোন কোন port listening অবস্থায় আছে তা দেখুন।
sudo ss -tulpn | grep -E '6443|10250|10256|8472'Default install-এ 6443 (API server), 10250 (kubelet) এবং 8472/udp (flannel overlay, যা VXLAN, virtual extensible LAN, ব্যবহার করে) প্রতিটি address-এ listening অবস্থায় থাকে। তাই এগুলো শুধু loopback-এ নয়, আপনার public address-এও থাকে। 10256 (kube-proxy health check)-ও দেখা যায়। তবে k3s এটিকে 127.0.0.1-এ bind করে (IPv6 node-এ ::1-এ), তাই অন্য machine থেকে scan করলে এটি দেখা যায় না।
কেন পোর্ট 6443 পুরো ক্লাস্টারের প্রবেশদ্বার
যে কেউ অ্যাডমিন অধিকার নিয়ে পোর্ট 6443-এ প্রমাণীকরণ করতে পারলে সে পড (pod) তৈরি করতে পারে, আর একটি পড হোস্ট মেশিনে রুট (root) ব্যবহারকারীর ক্ষমতা পেতে পারে। পোর্ট 6443 হলো পুরো মেশিনের প্রবেশদ্বার।
একটি খোলা 6443 পোর্ট মানেই তাৎক্ষণিক কোনো আপস নয়, কারণ Kubernetes পাসওয়ার্ড গ্রহণ করে না। এটি ক্লায়েন্ট সার্টিফিকেট বা বিয়ারার টোকেন (bearer token) দাবি করে। তবে দুটি বিষয় সবসময় সত্য।
প্রথমত, API সার্ভার কিছু অনুরোধের ক্ষেত্রে কোনো প্রমাণীকরণ ছাড়াই উত্তর দেয়। ডিফল্ট Kubernetes RBAC (role-based access control) গ্রুপ system:unauthenticated-কে system:public-info-viewer নামক একটি রোলের সাথে যুক্ত করে, যা /version, /healthz, /livez এবং /readyz-এর অনুমতি দেয়। অন্য একটি মেশিন থেকে:
curl -sk https://YOUR_SERVER_IP:6443/versionএটি আপনার Kubernetes-এর সঠিক সংস্করণটি জানিয়ে দেয়, যা CVE (common vulnerabilities and exposures) অনুসন্ধানের ইনপুট হিসেবে কাজ করে এবং এর মাধ্যমেই একটি স্ক্যানার আপনার সার্ভারটিকে আক্রমণযোগ্য হিসেবে চিহ্নিত করে। এই পাথগুলোর বাইরের যেকোনো অনুরোধ প্রত্যাখ্যান করা হয় এবং সেই প্রত্যাখ্যানের বার্তায় আপনার পরিচয় প্রকাশ পায়:
forbidden: User "system:anonymous" cannot get path "/api"দ্বিতীয়ত, API সার্ভারের প্রতিটি বাগ এই পোর্ট খোলা থাকা অবস্থায় দূর থেকে ব্যবহার করা সম্ভব। তাই এই অবস্থায় প্যাচিং ঐচ্ছিক বিষয় নয়, বরং বাধ্যতামূলক।
এর সহজ সমাধান হলো একটি ফায়ারওয়াল রুল। এখানে ufw-এর জন্য কেবল একটি লাইন যথেষ্ট নয়, কারণ k3s একই কার্নেলের মাধ্যমে ক্লাস্টার ট্রাফিক রাউট করে।
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 হলো ডিফল্ট পড নেটওয়ার্ক এবং 10.43.0.0/16 হলো ডিফল্ট সার্ভিস নেটওয়ার্ক। এগুলো ছাড়া ufw ক্লাস্টারের অভ্যন্তরীণ ট্রাফিক ড্রপ করে দেয়, ফলে পডগুলো API সার্ভার এবং একে অপরের সাথে যোগাযোগ বিচ্ছিন্ন হয়ে পড়ে। k3s-এর উদাহরণে সব জায়গা থেকে 6443 পোর্ট ব্যবহারের অনুমতি দেওয়া হয়েছে; সেটিকে আপনার নিজস্ব আইপি অ্যাড্রেস দিয়ে প্রতিস্থাপন করাই বুদ্ধিমানের কাজ। আপনি যদি ufw-এর সাথে পরিচিত না হন, তবে VPS-এর জন্য ufw ফায়ারওয়ালের মৌলিক বিষয়গুলো পড়ে নিতে পারেন, যা এই রুলগুলোর ওপর ভিত্তি করে তৈরি ডিফল্ট পলিসিগুলো ব্যাখ্যা করে।
k3s-এর ডকুমেন্টেশনে ওভারলে পোর্ট সম্পর্কে স্পষ্টভাবে বলা হয়েছে: "নোডগুলোর VXLAN পোর্ট ইন্টারনেটে উন্মুক্ত রাখা উচিত নয়, কারণ এটি আপনার ক্লাস্টার নেটওয়ার্ককে সবার জন্য উন্মুক্ত করে দেয়।" ইনকামিং ট্রাফিকের ওপর একটি ডিফল্ট ডিনাই (deny) পলিসি পোর্টটির নাম উল্লেখ না করেই এই সমস্যার সমাধান করে।
আরও শক্তিশালী সমাধান হলো পাবলিক অ্যাড্রেসের মাধ্যমে API-তে প্রবেশ বন্ধ করে VPN বা মেশ (mesh) অ্যাড্রেস ব্যবহার করা। সার্ভার সার্টিফিকেটে অবশ্যই সেই অ্যাড্রেসটি থাকতে হবে যা দিয়ে আপনি সংযোগ স্থাপন করেন, তাই এটিকে /etc/rancher/k3s/config.yaml-এ SAN (subject alternative name) হিসেবে যোগ করুন:
tls-san:
- 10.8.0.1
- k3s.example.com
secrets-encryption: truesudo systemctl restart k3s
sudo k3s secrets-encrypt statussecrets-encryption: true ডেটাস্টোরে থাকা Secret অবজেক্টগুলোকে এনক্রিপ্ট করে। k3s-এর ডকুমেন্টেশনে উল্লেখ আছে যে, "সার্ভার রিস্টার্ট না করে বিদ্যমান সার্ভারে Secrets-encryption চালু করা সম্ভব নয়", এবং এই পরিবর্তনের আগে লেখা Secret-গুলো sudo k3s secrets-encrypt reencrypt না চালানো পর্যন্ত পুরনো ফরম্যাটেই থেকে যায়। এটি কী সুবিধা দেয় তা পরিষ্কার থাকা প্রয়োজন। এটি ব্যাকআপ থেকে কপি করা ডেটাস্টোর ফাইলকে সুরক্ষা দেয়। কিন্তু যে ব্যক্তি API সার্ভারের সাথে যোগাযোগ করতে পারে, তার ক্ষেত্রে এটি কোনো সুরক্ষা দেয় না, কারণ API সার্ভার তাদের জন্যই Secret ডিক্রিপ্ট করে যাদের তা পড়ার অনুমতি আছে। একই পার্থক্য যেকোনো সেলফ-হোস্টেড সিক্রেট স্টোরের ক্ষেত্রে প্রযোজ্য, আর এ কারণেই Vaultwarden-এর নিরাপত্তা জোরদার করার প্রক্রিয়ায় এনক্রিপশনের চেয়ে অ্যাডমিন টোকেন এবং ব্যাকআপ ফাইলের সুরক্ষার ওপর বেশি গুরুত্ব দেওয়া হয়েছে।
কেন port 10250-এ kubelet গুরুত্বপূর্ণ
kubelet হলো সেই এজেন্ট যা কন্টেইনার চালু করে। port 10250-এ এর API পডগুলোর তালিকা দেখায় এবং সেগুলোর ভেতরে কমান্ড চালায়। যে kubelet বেনামী (anonymous) অনুরোধ গ্রহণ করে, তা মেশিনের প্রতিটি ওয়ার্কলোডের জন্য একটি রিমোট শেল হিসেবে কাজ করে।
আপনারটি পরীক্ষা করুন:
curl -sk https://127.0.0.1:10250/pods | head -c 60একটি বর্তমান k3s Unauthorized উত্তর দেয়, কারণ kubelet প্রতিটি কলারকে (caller) প্রমাণীকরণ (authenticate) এবং অনুমোদন (authorise) করার জন্য API সার্ভারকে অনুরোধ করে। যদি এর পরিবর্তে JSON পড তালিকা আসে, তবে বেনামী অ্যাক্সেস চালু আছে এবং যে কেউ port 10250-এ পৌঁছাতে পারলে আপনার কন্টেইনার থেকে তথ্য পড়তে এবং কমান্ড চালাতে পারবে।
যেকোনো উপায়েই এটিকে বাইরের দিক থেকে বন্ধ করুন। একটি সিঙ্গেল নোডে kubelet-এর একমাত্র ক্লায়েন্ট হলো একই মেশিনের কন্ট্রোল প্লেন এবং সেই ট্রাফিক loopback ইন্টারফেসের মাধ্যমে প্রবেশ করে, যা ufw ডিফল্টভাবে গ্রহণ করে। ইন্টারনেট থেকে 10250 ডিনাই (deny) করলে আপনার কোনো ক্ষতি হবে না। আপনি যদি কোনো অকার্যকর kubectl top বা metrics-server যা ঠিকমতো কাজ করছে না, তার কারণে এখানে এসে থাকেন, তবে এর কারণগুলো kubelet port 10250 errors-এ সংগ্রহ করা হয়েছে।
আপনার kubeconfig হলো একটি ক্লাস্টার অ্যাডমিন ক্রেডেনশিয়াল
k3s ফাইলটি /etc/rancher/k3s/k3s.yaml-এর মালিকানায় এবং 600 মোডে লেখে। এটি পরিবর্তন করার পরিণাম সম্পর্কে ডকুমেন্টেশনে বলা হয়েছে: "kubeconfig ফাইলটির মালিক root এবং এটি ডিফল্টভাবে 600 মোডে লেখা হয়। মোড 644-এ পরিবর্তন করলে হোস্টের অন্যান্য সাধারণ ব্যবহারকারীরা এটি পড়তে পারবে।"
এটিকে এভাবে বুঝুন: 644 মোড মানে হলো প্রতিটি লোকাল অ্যাকাউন্টই এখন একজন ক্লাস্টার অ্যাডমিনিস্ট্রেটর। অনেক ওয়াকথ্রুতেই ঠিক এই পরামর্শটি দেওয়া হয়, সাধারণত --write-kubeconfig-mode 644 হিসেবে, যাতে sudo ছাড়াই kubectl কাজ করে। এটি মূলত শেল অ্যাক্সেস থাকা যেকোনো ব্যক্তিকে অ্যাডমিন ক্রেডেনশিয়াল দিয়ে দেয়।
এর পরিবর্তে ফাইলটি শুধুমাত্র একটি নির্দিষ্ট ব্যবহারকারীর কাছে কপি করুন।
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.yaml600 root:root হলো আপনার কাঙ্ক্ষিত উত্তর। এই ফাইলটিতে system:masters-এর একজন সদস্যের জন্য একটি ক্লায়েন্ট সার্টিফিকেট থাকে; API সার্ভার এই গ্রুপটিকে শর্তহীনভাবে অনুমতি দেয়, তাই এর জন্য কখনোই RBAC রুল যাচাই করা হয় না। Kubernetes-এ কোনো সার্টিফিকেট রিভোকেশন লিস্ট নেই, যার মানে হলো একটি ফাঁস হওয়া কপি ততক্ষণ পর্যন্ত বৈধ থাকবে যতক্ষণ না আপনি ক্লাস্টার সার্টিফিকেট অথরিটি রোটেশন করছেন। এটিকে একটি 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 nat টেবিলের PREROUTING চেইনে DNAT (destination network address translation) রুল লেখে, এবং কোনো ফিল্টারিং সিদ্ধান্ত নেওয়ার আগেই PREROUTING কার্যকর হয়। গন্তব্যটি তখন একটি পড অ্যাড্রেস হয়ে যায়, যা হোস্ট নয়, তাই কার্নেল প্যাকেটটিকে FORWARD চেইনের দিকে পাঠিয়ে দেয় এবং এটি কখনোই INPUT চেইনের মধ্য দিয়ে যায় না। ufw-এর রুলগুলো INPUT-এ থাকে। প্যাকেটটি কখনোই সেগুলোর মুখোমুখি হয় না। জাম্পের ক্রমটি এখানে দেখা যাচ্ছে:
sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | headKUBE-SERVICES PREROUTING-এর শীর্ষে থাকে, এবং FORWARD-এ থাকা Kubernetes জাম্পগুলো ufw-এর নিজস্ব চেইনের উপরে অবস্থান করে। এটি সেই একই মেকানিজম যা Docker-কে ufw-এর বাইরে পোর্ট পাবলিশ করতে দেয়, এবং এর সমাধানগুলোও একই।
- আপনার প্রোভাইডারের নেটওয়ার্ক ফায়ারওয়ালে ফিল্টার করুন। এটি মেশিনের সামনে কাজ করে এবং আপনার কার্নেল কীভাবে রাউট করছে তা নিয়ে এটি চিন্তিত নয়।
- NodePort এবং LoadBalancer এড়িয়ে চলুন। সার্ভিসগুলোকে
ClusterIP-এ রাখুন এবং আপনার বিদ্যমান SSH সেশনের মাধ্যমেkubectl port-forwardব্যবহার করে সেগুলোতে পৌঁছান। - 80 এবং 443 পোর্টে একটি মাত্র ইনগ্রেস এক্সপোজ করুন, অন্য কিছু নয়।
kube-apiserver-arg-এর অধীনেservice-node-port-rangeব্যবহার করে রেঞ্জটি কমিয়ে আনুন, যাতে ভুলবশত কোনো NodePort এমন কোথাও না পড়ে যা আপনি পর্যবেক্ষণ করছেন না।
ufw এখনও ব্যবহার করা যুক্তিযুক্ত। এটি হোস্টের উদ্দেশ্যে আসা ট্র্যাফিক নিয়ন্ত্রণ করে, যেমন SSH এবং API সার্ভার। এটি কেবল পড ট্র্যাফিক নিয়ন্ত্রণ করে না, এবং এটি করবে বলে আশা করাই হলো সেই কারণ যার ফলে একটি ডাটাবেস সবার কাছে উন্মুক্ত হয়ে পড়ে।
hostPath বা privileged যুক্ত একটি pod আপনার VPS-এ root হিসেবে কাজ করে
কন্টেইনারগুলো আপনার কার্নেলের সাধারণ প্রসেস, যার কার্নেল দেখার ক্ষমতা সীমিত। বেশ কিছু pod ফিল্ড এই সীমাবদ্ধতাকে শিথিল করে দেয়।
securityContext.privileged: trueকন্টেইনারকে সমস্ত Linux capability এবং হোস্ট ডিভাইসের অ্যাক্সেস দেয়।hostPathহোস্টের একটি ডিরেক্টরিকে pod-এর ভেতর মাউন্ট করে। যে pod/-কে read-write মোডে মাউন্ট করে, সেটি/root/.ssh/authorized_keys-এ একটি কি (key) যুক্ত করতে পারে।hostPID: trueকন্টেইনারকে হোস্টের প্রসেস নেমস্পেসে নিয়ে যায়, যেখানে PID 1-এর বিরুদ্ধেnsenterচালালে হোস্টের শেল পাওয়া যায়।hostNetwork: trueএটিকে হোস্টের নেটওয়ার্ক স্ট্যাকে যুক্ত করে, যেখানে এটি হোস্টের পোর্ট বাইন্ড করতে পারে এবং loopback-এ থাকা সার্ভিসগুলোতে পৌঁছাতে পারে।
তাই "এখানে কে pod তৈরি করতে পারে" প্রশ্নটির উত্তর হলো "এই VPS-এ কে root"। যেকোনো ServiceAccount যার যেকোনো নেমস্পেসে pod তৈরির জন্য create ক্ষমতা আছে, সে root-এর সমতুল্য, যদি না অন্য কোনো ব্যবস্থা সেই pod-কে শুরুতেই প্রত্যাখ্যান করে।
সেই ব্যবস্থাটি হলো Pod Security admission, যা API server-এর ভেতরেই থাকে। সহজ কথায়, এটি প্রতিটি নেমস্পেসের জন্য একটি লেবেল এবং এর জন্য কোনো রিস্টার্টের প্রয়োজন হয় না।
kubectl label namespace default \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restrictedbaseline উপরের চারটি ফিল্ডকেই প্রত্যাখ্যান করে। restricted আরও কঠোর এবং এটি non-root ইউজার, একটি seccomp (secure computing mode) প্রোফাইল, কোনো privilege escalation না থাকা এবং ALL-এ capability কমিয়ে আনার দাবি করে, যা অনেক প্রকাশিত চার্টকে অকার্যকর করে দেয়। 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
EOFAPI 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)প্রতিটি নেমস্পেসে আলাদা লেবেল না দিয়ে পুরো ক্লাস্টারের জন্য ডিফল্ট সেটিংস করতে চাইলে, k3s /var/lib/rancher/k3s/server/psa.yaml-এ একটি admission কনফিগারেশন ফাইলের কথা উল্লেখ করে:
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 রিস্টার্ট করুন:
kube-apiserver-arg:
- 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'kube-system ছাড় (exemption) ঐচ্ছিক নয়। k3s-এর নিজস্ব ServiceLB pod-গুলো হোস্ট পোর্ট দাবি করে, যা baseline নিষিদ্ধ করে। তাই তালিকা থেকে kube-system বাদ দিলে, পরবর্তীবার যখন এই pod-গুলো পুনরায় তৈরি হবে, তখন সেগুলো প্রত্যাখ্যাত হবে। admission পরিবর্তনের পর k3s রিস্টার্ট করার সময় দ্বিতীয় একটি SSH সেশন খোলা রাখুন।
এখানে একটি বাড়তি সুবিধা হলো: k3s-এ একটি network policy controller থাকে এবং এটি ডিফল্টভাবেই চালু থাকে, তাই অতিরিক্ত কিছু ইনস্টল না করেই এই ক্লাস্টারে NetworkPolicy অবজেক্ট কার্যকর হয়। সব Kubernetes ডিস্ট্রিবিউশনে এটি থাকে না, এবং একটি compromised pod যাতে অন্য কোথাও পৌঁছাতে না পারে, তা আটকানোর জন্য এটিই সেরা টুল।
যেসব bundled component আপনি ব্যবহার করেন না সেগুলো সরিয়ে ফেলুন
ইন্সটলারটি বেশ কিছু add-on মোতায়েন করে। প্রতিটি add-on একটি listener যোগ করে এবং প্যাচ করার জন্য বাড়তি একটি বিষয় তৈরি করে। --disable এই মানগুলো গ্রহণ করে: coredns, servicelb, traefik, local-storage, metrics-server, runtimes।
coredns বজায় রাখুন। এটি ছাড়া ক্লাস্টারের কোনো কিছুই নাম resolve করতে পারে না। বাকিগুলো আপনার পছন্দের ওপর নির্ভর করে। /etc/rancher/k3s/config.yaml-এ:
disable:
- traefik
- servicelb
disable-helm-controller: truesudo systemctl restart k3s
kubectl get pods -Ak3s আপনার নিষ্ক্রিয় করা componentগুলো মুছে ফেলে, তাই traefik pod এবং svclb- podগুলো নিজে থেকেই অদৃশ্য হয়ে যায়। আগে এর ফলাফলগুলো জেনে নিন। servicelb না থাকলে, প্রতিটি type: LoadBalancer service চিরকাল <pending>-এ আটকে থাকে, কারণ সেগুলোকে ঠিকানা দেওয়ার মতো কেউ থাকে না। traefik না থাকলে কোনো ingress controller থাকে না, তাই Ingress objectগুলো কোনো কাজই করে না। যখন আপনি অন্য কোনো উপায়ে traffic পরিচালনা করেন, যেমন host-এ একটি reverse proxy ব্যবহার করেন, তখন এগুলো নিষ্ক্রিয় করুন; আর যখন এগুলো ব্যবহার করেন তখন এগুলোকে স্পর্শ করবেন না। disable-helm-controller: true সেই controller-টিকে সরিয়ে ফেলে যা HelmChart resource পর্যবেক্ষণ করে। আপনি যদি নিজে helm চালান, তবে এই privileged component-টি আপনার প্রয়োজন নেই।
ডিফল্ট ServiceAccount টোকেন অটোমাউন্ট বন্ধ করা
আপনি অন্য কিছু উল্লেখ না করলে প্রতিটি পড ডিফল্টভাবে /var/run/secrets/kubernetes.io/serviceaccount/token-এ একটি ServiceAccount টোকেন পায়। default ServiceAccount-এর কোনো RBAC পারমিশন থাকে না, তাই শুধুমাত্র এই টোকেন দিয়ে খুব বেশি কিছু করা সম্ভব নয়। তবে একটি compromised কন্টেইনারের ভেতরে থাকা আক্রমণকারীর জন্য এটি একটি বৈধ ক্রেডেনশিয়াল এবং API সার্ভারে প্রবেশের সুযোগ তৈরি করে দেয়, যা ক্লাস্টার এসকেলেশনের অধিকাংশ কৌশলের প্রথম ধাপ।
k3s হার্ডেনিং গাইড অনুযায়ী প্রতিটি নেমস্পেসের জন্য এটি বন্ধ রাখা হয়:
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}'এটি কার্যকর হয়েছে কি না তা যাচাই করুন। পডটি চালু হওয়ার জন্য কয়েক সেকেন্ড সময় দিন।
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 সেট করে নিতে পারে, ফলে কোনো কিছুই স্থায়ীভাবে বন্ধ হয়ে যায় না। এটি নিজেই একটি ছোট জয়। বড় সুবিধাটি হলো কোনো ওয়ার্কলোডকে অপ্রয়োজনীয় পারমিশনসহ ServiceAccount না দেওয়া। একটি টোকেনের বর্তমান ভ্যালু কত তা আপনি এভাবে দেখতে পারেন:
kubectl auth can-i --list --as=system:serviceaccount:default:defaultরিসোর্স লিমিট, যাতে একটি পড পুরো ক্লাস্টারকে অচল করে দিতে না পারে
একটি সিঙ্গেল নোডে কন্ট্রোল প্লেন এবং আপনার ওয়ার্কলোডগুলো একই কার্নেল ও মেমোরি পুল শেয়ার করে। মেমোরি লিক হওয়া কোনো পড সবসময় একা মারা যায় না। কার্নেলের OOM (আউট অফ মেমোরি) কিলার বড় প্রসেসগুলোর ওপর ভিত্তি করে তার শিকার বেছে নেয়, আর 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 পুরো নেমস্পেসের জন্য রিসোর্স ব্যবহারের সীমা নির্ধারণ করে:
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 রিপোর্ট করে এবং নোডের বাকি অংশ সচল থাকে। কিন্তু পুরো নোডের মেমোরি শেষ হয়ে গেলে dmesg-এ একটি Killed process লাইন দেখা যায় এবং সাধারণত এটি আশেপাশের সার্ভিসগুলোকেও অচল করে দেয়। প্রথম ঘটনাটি প্রমাণ করে যে আপনার লিমিট সঠিকভাবে কাজ করছে। দ্বিতীয় ঘটনাটি হলো সেই পরিস্থিতি, যা প্রতিরোধের জন্যই লিমিট ব্যবহার করা হয়।
CI-তে manifest এবং schedule অনুযায়ী k3s cluster scan করুন
দুটি জায়গায় scanning করা উচিত, এবং প্রতিটি ভিন্ন ধরনের সমস্যা শনাক্ত করে। প্রথমে Trivy install করুন। Project-টি প্রতিটি release-এর সঙ্গে Debian package প্রকাশ করে। 11 October 2026 তারিখে 0.75.0 সর্বশেষ ছিল। তাই automation-এ এটি নির্দিষ্ট version হিসেবে pin করার আগে releases page-এ নতুন version আছে কি না পরীক্ষা করুন।
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প্রথম জায়গাটি হলো আপনার manifest, cluster-এ পাঠানোর আগে।
trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deployকোনো finding পাওয়া গেলে --exit-code 1 CI (continuous integration) job ব্যর্থ করে। প্রতিটি result-এ ব্যর্থ field এবং তার severity উল্লেখ থাকে। তাই commit করার উদ্দেশ্য না থাকা কোনো privileged: true API server-এ পৌঁছানোর বদলে build ব্যর্থ করে। গ্রহণ করার সিদ্ধান্ত নেওয়া কোনো finding .trivyignore file-এ রাখুন। এতে সেই সিদ্ধান্তটি যে manifest-এর কারণে নেওয়া হয়েছে, তার পাশে git-এ সংরক্ষিত থাকে।
দ্বিতীয় জায়গাটি হলো চলমান cluster, নির্দিষ্ট schedule অনুযায়ী।
trivy k8s --compliance=k8s-cis-1.23 --report summaryএই command সম্পর্কে একটি গুরুত্বপূর্ণ বিষয় আছে। trivy k8s এমন একটি node collector pod deploy করে, যার node-level settings পরীক্ষা করার জন্য host access প্রয়োজন। যে cluster-এ আপনি privileged pod প্রত্যাখ্যান করা শুরু করেছেন, সেখানে এটি লক্ষ্য করার মতো বিষয়; এড়িয়ে যাওয়ার জন্য workaround করা উচিত নয়। trivy k8s --report summary --disable-node-collector collector বাদ দেয়, ফলে node-level checks-ও বাদ যায়।
kube-bench host side পরীক্ষা করে: file permission এবং process flag। CIS (Center for Internet Security) benchmark-এর বেশিরভাগ অংশ এখানেই থাকে। এতে k3s profile রয়েছে। Upstream Job manifest নিন এবং সেটি সামঞ্জস্য করুন।
curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yamlContainer command-টি ["kube-bench", "--benchmark", "k3s-cis-1.7"]-এ পরিবর্তন করুন। /etc/kubernetes এবং /var/lib/etcd mount-এর বদলে /etc/rancher এবং /var/lib/rancher mount ব্যবহার করুন, কারণ k3s তার file সেখানেই রাখে। এরপর:
kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1লক্ষ্য করুন, ওই Job-এ কী আছে: hostPID: true এবং host directory mount। এটি ঠিক সেই pod structure, যা আপনি আগের section-এ প্রত্যাখ্যান করা শুরু করেছিলেন। এটিকে exempted kube-system namespace-এ চালান, output পড়ুন, তারপর kubectl delete job kube-bench। Scanner-এর privileged হওয়া privileged workload নিষিদ্ধ করার নীতি বন্ধ করার কারণ নয়।
Image content আলাদা বিষয়। trivy image ghcr.io/example/app:1.4 image-এর ভেতরের package database পড়ে এবং যেগুলো vulnerable বলে জানা আছে সেগুলোর তালিকা তৈরি করে। আপনি যখন আপনার server-এ পরিচিত CVE পরীক্ষা করেন, এটিও একই ধরনের কাজ করে।
এবার scanner output সম্পর্কে বাস্তব বিষয়টি বলি। একটি single-node hobby cluster CIS control-এর দীর্ঘ তালিকায় ব্যর্থ হবে। এই ব্যর্থতাগুলোর বেশিরভাগই সঠিক, কিন্তু আপনার ক্ষেত্রে প্রাসঙ্গিক নয়। Benchmark-টি multi-node, multi-tenant cluster-এর জন্য লেখা: আলাদা host-এ etcd, machine-এর বাইরে পাঠানো audit log, আলাদা kubelet certificate authority, এবং এমন compliance regime-এর জন্য admission plugin, যার অধীনে আপনি পড়েন না। k3s ইচ্ছাকৃতভাবে control plane-কে একটি config file-সহ single process হিসেবে চালায়। তাই kube-scheduler manifest file-এর permission পরীক্ষা করা control pass করতে পারে না, কারণ এমন কোনো file-ই নেই।
এই ক্রমে failure পড়ুন এবং value শেষ হয়ে গেলে থামুন: /etc/rancher এবং /var/lib/rancher-এর অধীনে file mode ও ownership, anonymous বা unauthenticated access উল্লেখ করা যেকোনো বিষয়, কোনো component 0.0.0.0-এ bind করা আছে এমন report, এবং অকারণে UID 0 হিসেবে চলা যেকোনো container। বাকি বিষয়গুলো দ্বিতীয় node বা access থাকা দ্বিতীয় ব্যক্তির জন্য অপেক্ষা করতে পারে। উপেক্ষা করা একশো লাইনের report-এর চেয়ে কার্যকর করা পাঁচ লাইনের report বেশি মূল্যবান।
একটি সিঙ্গেল নোডের জন্য k3s হার্ডেনিং নির্দেশিকা
- ufw-এর ডিফল্ট incoming পলিসি deny-তে সেট করুন, SSH-এর অনুমতি দিন, আপনার নিজস্ব IP থেকে 6443 পোর্টের অ্যাক্সেস দিন এবং pod ও service নেটওয়ার্কের অনুমতি দিন।
- kubeconfig ফাইলটি আপনার ব্যবহারকারীর কাছে 600 মোডে কপি করুন এবং কখনোই
--write-kubeconfig-mode 644সেট করবেন না। - নিশ্চিত করুন যে 10250 পোর্টে kubelet বেনামী অনুরোধ প্রত্যাখ্যান করছে এবং পোর্টটিকে ইন্টারনেটের নাগালের বাইরে রাখুন।
- যে bundled কম্পোনেন্টগুলো আপনি ব্যবহার করেন না সেগুলো নিষ্ক্রিয় করুন, তারপর k3s রিস্টার্ট করুন।
- Pod Security admission-এর জন্য আপনার namespace-গুলোকে
enforce=baselineএবংwarn=restrictedদিয়ে লেবেল করুন। - ডিফল্ট ServiceAccount টোকেন অটোমাউন্টিং বন্ধ করুন।
- একটি LimitRange এবং ResourceQuota যোগ করুন, এবং k3s-এর জন্য CPU ও মেমরি রিজার্ভ করুন।
- CI-তে
trivy fs --scanners misconfigযোগ করুন এবং প্রতি মাসে একটি CIS স্ক্যান চালান।
নিচের হোস্ট মেশিনটির জন্য অন্য যেকোনো সার্ভারের মতোই যত্ন প্রয়োজন এবং k3s সেখানে বাড়তি কিছু জটিলতা যোগ করে। এটি apt-এর পরিবর্তে একটি স্ক্রিপ্ট দ্বারা ইনস্টল করা হয়েছে, তাই apt upgrade এটিকে কখনোই আপডেট করবে না। Ubuntu-তে unattended upgrades ব্যবহার করে অপারেটিং সিস্টেমকে নিজস্ব শিডিউলে প্যাচযুক্ত রাখুন এবং কাঙ্ক্ষিত চ্যানেল বা ভার্সন ব্যবহার করে ইনস্টলারটি পুনরায় চালানোর মাধ্যমে k3s-কে সতর্কতার সাথে আপগ্রেড করুন। একটি মেশিনে দুটি ভিন্ন আপডেট পাথ মনে রাখা কঠিন হতে পারে, তাই কোন কম্পোনেন্টটি কীভাবে আপডেট হবে তা লিখে রাখুন।
FAQ
ইন্টারনেটে 6443 পোর্টে k3s API server উন্মুক্ত রাখা কি নিরাপদ?
এটি নিজে থেকেই কোনো উন্মুক্ত দরজা নয়, কারণ API server-এর জন্য একটি client certificate বা token প্রয়োজন হয় এবং অন্য যেকোনো অনুরোধ 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 সার্ভিসকে ব্লক করছে না?
কারণ প্যাকেটটি কখনোই সেই চেইনে পৌঁছায় না যেখানে আপনার রুলটি রয়েছে। kube-proxy nat টেবিলের PREROUTING চেইনে DNAT রুল বসায়, যা সবার আগে চলে এবং গন্তব্য পরিবর্তন করে একটি pod অ্যাড্রেসে পাঠিয়ে দেয়। প্যাকেটটি তখন লোকালি ডেলিভার হওয়ার পরিবর্তে ফরওয়ার্ড করা হয়, তাই এটি FORWARD চেইন দিয়ে যায় এবং INPUT চেইনকে এড়িয়ে যায়, যেখানে ufw-এর রুলগুলো থাকে। sudo iptables -S PREROUTING -t nat | head দিয়ে এটি নিশ্চিত করুন। আপনার প্রোভাইডারের নেটওয়ার্ক ফায়ারওয়ালে NodePort ফিল্টার করুন, অথবা type: NodePort এড়িয়ে চলুন এবং পরিবর্তে kubectl port-forward দিয়ে সার্ভিসগুলোতে পৌঁছান।
আমার কি --write-kubeconfig-mode 644 দিয়ে k3s চালানো উচিত?
না। k3s ডকুমেন্টেশনে স্পষ্টভাবে বলা আছে এটি কী করে: "মোড 644-এ পরিবর্তন করলে হোস্টের অন্যান্য সাধারণ ব্যবহারকারীরাও এটি পড়তে পারবে।" এই ফাইলে system:masters-এর জন্য একটি client certificate থাকে, তাই 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 উল্লেখ করতে হবে।" এটি স্পষ্টভাবে উল্লেখ করুন, কারণ অটো-ডিটেকশন kubeadm লেআউট ধরে নেয়, যেখানে k3s তার ফাইলগুলো /etc/rancher এবং /var/lib/rancher-এর অধীনে রাখে। এমন কিছু ব্যর্থতা আশা করতে পারেন যা একটি সিঙ্গেল নোডের ক্ষেত্রে প্রযোজ্য নয়, এবং ফাইল পারমিশন ও বেনামী অ্যাক্সেসের বিষয়গুলোতে আগে ব্যবস্থা নিন।
Pod Security admission কি k3s-এর বান্ডেল করা কম্পোনেন্টগুলোকে অকেজো করে দেবে?
যদি আপনি এটি kube-system-এ এনফোর্স করেন তবে তা হবে। k3s যে ServiceLB পডগুলো type: LoadBalancer সার্ভিসের জন্য তৈরি করে, সেগুলো হোস্টে পোর্ট দাবি করে, এবং baseline হোস্ট পোর্ট নিষিদ্ধ করে, তাই পরবর্তীবার পডগুলো পুনরায় তৈরি হওয়ার সময় সেগুলো প্রত্যাখ্যাত হয়। অ্যাডমিশন কনফিগারেশন ফাইলে kube-system-কে ছাড় দিন, অথবা শুধুমাত্র আপনার নিজের তৈরি করা নেমস্পেসগুলোতে লেবেল হিসেবে Pod Security প্রয়োগ করুন। enforce=baseline এবং warn=restricted দিয়ে শুরু করুন যাতে এটি চালু করার আগে আপনি পড়তে পারেন যে restricted কী কী অকেজো করতে পারে।