সরাসরি পর্বে যান
পর্ব ৪৯ · ৮১-এর মধ্যে · মডিউল ৮

Kubernetes নিরাপত্তা — RBAC, NetworkPolicy আর Pod Security

Kubernetes Security — RBAC, NetworkPolicy and Pod Security

Taught in Bangla, this lesson covers the 4C model, authentication, authorization and admission at the API server, Role and ClusterRole bindings verified with can-i, short-lived ServiceAccount tokens, NetworkPolicy default deny and why enforcement depends on the CNI plugin, Pod Security Standards and Admission, securityContext hardening, policy engines, audit logs and runtime detection, with simulators.

এই পর্বে যা শিখবেন

  • 4C model আর API server-এর তিন দরজা, ৪০১ ও ৪০৩-এর আসল পার্থক্য
  • Role, ClusterRole, RoleBinding, ClusterRoleBinding বেছে নেওয়া আর can-i দিয়ে যাচাই করা
  • ServiceAccount-এর স্বল্পমেয়াদি token কীভাবে কাজ করে, আর আসল cluster-এ এর মেয়াদ কত দেখাল
  • NetworkPolicy দিয়ে default deny আর একটি পথ খোলা, আর enforcement যাচাই করা
  • Pod Security Standards, securityContext, policy engine, audit log আর runtime detection-এর জায়গা চেনা

এই পর্বের আউটপুট কোথা থেকে: kubectl-এর সব transcript একটি স্থানীয় single-node cluster-এ (Kubernetes ১.৩৪) আসল run থেকে নেওয়া। নাম, age, IP আর সময় আপনার cluster-এ আলাদা হবে। লম্বা আউটপুট ছোট করে … দিয়ে দেখানো হয়েছে। Browser-এ kubectl চলে না, তাই যা বদলে দেখা যায়, তার জন্য তিনটি JavaScript সিমুলেটর আছে। আগের পর্বগুলোর Pod, Deployment আর Service জানা থাকলে সুবিধা হবে।

১ · চারটি স্তর — 4C model

আবাসিক এলাকার একটি ফ্ল্যাটবাড়ি ভাবুন। এলাকার রাস্তায় পাহারা আছে। বাড়ির গেটে দারোয়ান আছেন। ফ্ল্যাটের দরজায় তালা আছে। আর ফ্ল্যাটের ভেতরে আলমারিতে আলাদা তালা। গেট খোলা থাকলে আলমারির তালা কিছুটা বাঁচায়, কিন্তু আলমারি খোলা রেখে শুধু গেটের ভরসায় থাকাও ভুল। এই ভাবনাকেই defense in depth বলে, যা পর্ব ৩৪-এ দেখেছেন।

Cloud-native নিরাপত্তায় এই স্তরগুলোকে সহজে মনে রাখার একটি ছক হলো 4C model: Cloud, Cluster, Container, Code. Kubernetes-এর পুরোনো নথিতে এটি জনপ্রিয় হয়েছিল। পরে নথির কাঠামো বদলেছে, তবে মনে রাখার ছক হিসেবে এটি এখনো কাজের।

স্তরকী রক্ষা করেউদাহরণকোথায় পড়বেন
CloudNode, network, cloud accountIAM, private subnet, API server-এর সীমিত প্রবেশপর্ব ৩১, পর্ব ২৭
ClusterAPI server, etcd, Pod-এর trafficRBAC, NetworkPolicy, admissionএই পর্ব
ContainerImage আর চলন্ত containerNon-root, capability বাদ, image স্বাক্ষরএই পর্ব, পর্ব ৪১
Codeআপনার applicationInput যাচাই, dependency আপডেট, TLSপর্ব ৬১ (DevSecOps)

Managed Kubernetes-এ shared responsibility মনে রাখুন। Provider সাধারণত control plane চালান। কিন্তু RBAC, NetworkPolicy, Pod-এর সেটিং আর আপনার image আপনারই দায়িত্ব। দায়ের সীমা provider-ভেদে আলাদা, তাই তাঁদের নথি দেখে নিন।

দুটি প্রকাশ্য ঘটনা মনে রাখার মতো। ২০১৮ সালের ফেব্রুয়ারিতে নিরাপত্তা-প্রতিষ্ঠান RedLock জানায়, Tesla-র একটি Kubernetes console পাসওয়ার্ড ছাড়া খোলা ছিল, সেখান থেকে cloud credential বেরিয়ে যায় আর cryptomining চালানো হয়। প্রতিবেদন অনুযায়ী Tesla জানিয়েছিল, গ্রাহকের তথ্য ক্ষতিগ্রস্ত হওয়ার প্রমাণ মেলেনি। আর ২০১৮ সালের ডিসেম্বরে Kubernetes-এর API server-এ একটি গুরুতর ত্রুটি (CVE-2018-1002105) প্রকাশ পায়, যার সমাধান এসেছিল version আপডেটে। প্রথম ঘটনার শিক্ষা হলো ভুল সেটিং, দ্বিতীয়টির শিক্ষা নিয়মিত আপডেট।

২ · API server-এর তিন দরজা

Cluster-এর সব কাজ এক জায়গা দিয়ে যায়: API server. kubectl, controller, kubelet, সবাই এর সাথেই কথা বলে। তাই নিরাপত্তার মূল পাহারাও এখানেই।

একটি অফিস ভবনে ঢুকছেন। রিসেপশনে প্রথমে পরিচয়পত্র দেখা হয়: আপনি কে? এটি authentication. তারপর তালিকা দেখা হয়: আপনার কি এই তলায় যাওয়ার অনুমতি আছে? এটি authorization. শেষে ব্যাগ তল্লাশি: অনুমতি থাকলেও ভেতরে নিষিদ্ধ জিনিস নেওয়া যাবে না। এটি admission control. সব পেরোলে রেজিস্টারে নাম ওঠে, আর সেই রেজিস্টার হলো etcd.

একটি request-এর পথ — API server-এর নিরাপত্তা দরজা

A request through the API server's security gates

kubectl থেকে API server-এর পাঁচ ধাপ পেরিয়ে etcd পর্যন্ত request-এর পথ kubectl API server-এর ভেতরে Authentication আপনি কে? Authorization (RBAC) এই কাজের অনুমতি আছে? Mutating admission spec-এ কিছু যোগ বা বদল Schema validation গঠন ঠিক আছে? Validating admission নিয়ম মানছে? (PSA, policy) etcd এখানে জমা হলো 401 অজানা 403 অনুমতি নেই ভুল গঠন নিয়মে বাতিল
ছবি ৪৯.১ — আগে পরিচয়, তারপর অনুমতি, তারপর বিষয়বস্তুর নিয়ম। Mutating ধাপ আগে চলে, তাই validating ধাপ চূড়ান্ত spec-টি দেখে।

৪০১ আর ৪০৩ গুলিয়ে ফেলা খুব সাধারণ ভুল। আসল cluster-এ দুটোই দেখুন। প্রথমটিতে একটি ভুয়া token দেওয়া হয়েছে, দ্বিতীয়টিতে পরিচয় ঠিক আছে কিন্তু অনুমতি নেই:

# kubeconfig with a fake token
$ kubectl --kubeconfig bad.kubeconfig get pods -n abcl-kx
error: You must be logged in to the server (Unauthorized)

# valid identity, no permission
$ kubectl get secrets -n abcl-kx \
    --as=system:serviceaccount:abcl-kx:report-app
Error from server (Forbidden): secrets is forbidden: User
"system:serviceaccount:abcl-kx:report-app" cannot list resource "secrets"
in API group "" in the namespace "abcl-kx"

Unauthorized আর Forbidden শব্দ দুটিই ঠিকানা দেয়। প্রথমটি মানে API server আপনাকে চিনল না। দ্বিতীয়টি মানে চিনল, কিন্তু কাজটি করতে দিল না। Forbidden মেসেজে user, verb আর resource তিনটিই লেখা থাকে, তাই কোন অনুমতি নেই তা সেখানেই পড়া যায়।

Kubernetes নিজে সাধারণ মানুষের user account জমা রাখে না। মানুষের পরিচয় আসে বাইরে থেকে: client certificate, OIDC token বা cloud provider-এর ব্যবস্থা। শুধু ServiceAccount Kubernetes-এর ভেতরের বস্তু। এই cluster-এ authorization কীভাবে সাজানো, তা API server-এর flag-এ দেখা যায়:

$ kubectl get pod -n kube-system -l component=kube-apiserver -o yaml \
    | grep -E -e '--authorization-mode|--enable-admission'
      - --authorization-mode=Node,RBAC
      - --enable-admission-plugins=NodeRestriction

Node মোড kubelet-এর অনুরোধ সামলায়, RBAC বাকিদের। এবার পুরো পথটি ধাপে ধাপে দেখুন। Namespace-টিতে আগে থেকেই restricted নিয়ম চালু করা আছে, যা ৬ নম্বর অংশে দেখবেন।

  1. kubectlPOST podsTLS-এ পাঠাল
  2. Authnকে?পরিচয়
  3. AuthzRBACঅনুমতি
  4. Mutatingযোগspec বদল
  5. ValidatingPSAনিয়ম যাচাই
  6. etcdcreatedজমা হলো
Terminalঅনুরোধ পাঠানো
$ kubectl apply -f 32-hardened.yaml
(kubectl turns the YAML into an HTTPS request)
POST /api/v1/namespaces/abcl-kx/pods
পরিচয়একটি ServiceAccount-এর আসল run
$ kubectl auth whoami --as=system:serviceaccount:abcl-kx:report-app
ATTRIBUTE   VALUE
Username    system:serviceaccount:abcl-kx:report-app
Groups      [system:serviceaccounts
             system:serviceaccounts:abcl-kx system:authenticated]
অনুমতিআসল run
$ kubectl auth can-i list pods -n abcl-kx --as=...:report-app
yes
$ kubectl auth can-i create deployments -n abcl-kx --as=...:report-app
no
Pod-এ যা যোগ হলোআসল run, ছোট করা
name: kube-api-access-x8rks
sources:
  serviceAccountToken:
    expirationSeconds: 3607
    path: token
  configMap: …
  downwardAPI: …
Pod Securityঅন্য একটি Pod বাতিল হলো, আসল run
$ kubectl apply -f 31-plain.yaml
Error from server (Forbidden): … pods "plain-nginx" is forbidden:
violates PodSecurity "restricted:v1.34": allowPrivilegeEscalation != false …
Terminalসব দরজা পেরিয়েছে
$ kubectl apply -f 32-hardened.yaml
pod/hardened created
$ kubectl wait --for=condition=Ready pod/hardened -n abcl-kx
pod/hardened condition met
  1. অনুরোধ রওনা হলো। kubectl YAML পড়ে API server-কে একটি HTTPS অনুরোধ পাঠায়। সাথে যায় পরিচয়ের প্রমাণ, যেমন certificate বা token.
  2. Authentication. API server প্রমাণ যাচাই করে একটি নাম আর কিছু group পায়। প্রমাণ অচল হলে এখানেই ৪০১ ফেরে। পরের কোনো ধাপ চলে না।
  3. Authorization. এবার প্রশ্ন: এই নাম এই namespace-এ এই verb কি চালাতে পারে? RBAC নিয়ম খোঁজে। মিল না পেলে ৪০৩. এই ধাপে Pod-এর বিষয়বস্তু দেখা হয় না।
  4. Mutating admission. অনুরোধ এখন বদলাতে পারে। যেমন ServiceAccount admission Pod-এ একটি projected volume যোগ করে, আসল run-এ তার নাম kube-api-access-x8rks. আপনি এটি YAML-এ লেখেননি।
  5. Validating admission. Schema যাচাইয়ের পর এই ধাপে শুধু হ্যাঁ বা না। Pod Security নিয়ম না মানা plain-nginx এখানেই বাতিল হয়েছে, যদিও অনুরোধকারীর অনুমতি ছিল।
  6. etcd-তে জমা। সব পেরোলে বস্তুটি etcd-তে লেখা হয়। তারপর scheduler আর kubelet নিজের কাজ শুরু করে। Hardened Pod এই পথেই চালু হয়েছে।
মূল ধারণা

kubectl apply --dry-run=server অনুরোধটি এই সব দরজা দিয়েই পাঠায়, শুধু etcd-তে লেখে না। তাই ঝুঁকিপূর্ণ manifest পরীক্ষার সবচেয়ে নিরাপদ পথ এটি। এই পর্বের privileged Pod-টিও এভাবেই পরীক্ষা হয়েছে। সেটি কখনো চলেনি।

৩ · RBAC — কে, কোথায়, কী করতে পারবে

RBAC-এর ধারণা পর্ব ৩২-এর IAM-এর মতোই, তবে এখানে দুটি প্রশ্ন আলাদা করা হয়। অনুমতির তালিকা কী? সেই তালিকা কার হাতে, আর কোন এলাকায়? প্রথমটি Role, দ্বিতীয়টি Binding. রেলের টিকিট চেকারের কথা ভাবুন: “ডিউটির নিয়ম” একটি কাগজ, আর “কে কোন ট্রেনে ডিউটি করবেন” আরেকটি কাগজ।

বস্তুকী ধরেএলাকা
Roleঅনুমতির তালিকাএকটি namespace
ClusterRoleঅনুমতির তালিকা, namespace ছাড়াপুরো cluster-এর জন্য লেখা, বা বারবার ব্যবহারের ছাঁচ
RoleBindingSubject-কে Role বা ClusterRole দেওয়াশুধু নিজের namespace
ClusterRoleBindingSubject-কে ClusterRole দেওয়াপুরো cluster, সব namespace

একটি কাল্পনিক অনলাইন বাজার অ্যাপের রিপোর্ট সার্ভিসের শুধু Pod-এর তালিকা দেখা দরকার। আমরা তাকে ঠিক সেটুকুই দিই:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: report-app
  namespace: abcl-kx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: abcl-kx
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: report-app-pod-reader
  namespace: abcl-kx
subjects:
  - kind: ServiceAccount
    name: report-app
    namespace: abcl-kx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-reader

এবার নিজের অনুমতি নয়, ওই ServiceAccount-এর চোখে দেখা যাক। --as flag হলো impersonation: অন্যের পরিচয়ে প্রশ্ন করা। এতে আসল আচরণ দেখা যায়, অনুমান নয়।

$ SA=system:serviceaccount:abcl-kx:report-app
$ kubectl auth can-i list pods -n abcl-kx --as=$SA
yes
$ kubectl auth can-i delete pods -n abcl-kx --as=$SA
no
$ kubectl auth can-i get secrets -n abcl-kx --as=$SA
no
$ kubectl auth can-i list pods -n default --as=$SA
no
$ kubectl auth can-i --list -n abcl-kx --as=$SA
Resources   Non-Resource URLs   Resource Names   Verbs
…
pods        []                  []               [get list watch]
…

ফল চারটি স্পষ্ট কথা বলে। পড়া যায়, মোছা যায় না। Secret দেখা যায় না। আর RoleBinding নিজের namespace-এর বাইরে কাজ করে না। no-র সময় exit code ছিল ১, তাই CI script-এ এটি পরীক্ষা হিসেবে বসানো যায়।

ClusterRole আসলে দুই কাজে লাগে

Built-in ClusterRole view-কে RoleBinding দিয়ে এক namespace-এ দিলে ক্ষমতা সেখানেই আটকে থাকে। আর ClusterRoleBinding দিলে পুরো cluster-এ ছড়ায়। তাই একই ছাঁচ অনেক namespace-এ আলাদা করে ব্যবহার করা যায়:

# AUD=system:serviceaccount:abcl-kx:auditor
# auditor has ClusterRole "view" through a RoleBinding in abcl-kx
$ kubectl auth can-i get deployments.apps -n abcl-kx --as=$AUD
yes
$ kubectl auth can-i get secrets -n abcl-kx --as=$AUD
no
$ kubectl auth can-i get pods -n default --as=$AUD
no

# a ClusterRoleBinding adds one cluster-scoped permission
$ kubectl auth can-i list namespaces --as=$AUD
no
$ kubectl apply -f 12-crb.yaml
clusterrole.rbac.authorization.k8s.io/abcl-kx-ns-reader created
clusterrolebinding.rbac.authorization.k8s.io/abcl-kx-ns-reader created
$ kubectl auth can-i list namespaces --as=$AUD
Warning: resource 'namespaces' is not namespace scoped

yes

view ইচ্ছা করেই Secret বাদ রাখে। তাই এটি নিরীক্ষকের জন্য ভালো শুরু। কিন্তু built-in edit ধরনের role-এ Secret পড়ার ক্ষমতা থাকে, তাই দেওয়ার আগে kubectl describe clusterrole edit পড়ে নিন। শেষ ফলাফলের Warning-টি শুধু জানায়, namespace বস্তুর ওপর can-i কোনো namespace-এ সীমাবদ্ধ নয়। পরীক্ষা শেষে এই দুটি cluster-স্তরের বস্তু নাম ধরে মুছে ফেলা হয়েছে।

RBAC-এর মূল্যায়ন নিজে চালিয়ে দেখুন। BINDINGS বা ASK বদলে আবার চালান:

// RBAC evaluator: rules only add permission. Edit BINDINGS or ASK and run again.
const ROLES = {
  'Role/pod-reader':     [{ res: ['pods'], verbs: ['get', 'list', 'watch'] }],
  'ClusterRole/view':    [{ res: ['pods', 'services'], verbs: ['get', 'list', 'watch'] }],
  'ClusterRole/ns-read': [{ res: ['namespaces'], verbs: ['get', 'list'] }],
  'Role/do-everything':  [{ res: ['*'], verbs: ['*'] }],
};
const BINDINGS = [
  { kind: 'RoleBinding', ns: 'shop', who: 'report-app', ref: 'Role/pod-reader' },
  { kind: 'RoleBinding', ns: 'shop', who: 'auditor', ref: 'ClusterRole/view' },
  { kind: 'ClusterRoleBinding', who: 'auditor', ref: 'ClusterRole/ns-read' },
  { kind: 'RoleBinding', ns: 'shop', who: 'lazy-app', ref: 'Role/do-everything' },
];
const ASK = [
  ['report-app', 'list', 'pods', 'shop'],
  ['report-app', 'delete', 'pods', 'shop'],
  ['report-app', 'list', 'pods', 'bank'],
  ['auditor', 'get', 'secrets', 'shop'],
  ['auditor', 'list', 'namespaces', '(cluster)'],
  ['lazy-app', 'get', 'secrets', 'shop'],
  ['lazy-app', 'get', 'secrets', 'bank'],
];
const hit = (list, x) => list.includes('*') || list.includes(x);
function check(who, verb, res, ns) {
  for (const b of BINDINGS) {
    if (b.who !== who) continue;
    if (b.kind === 'RoleBinding' && b.ns !== ns) continue; // scope of a RoleBinding
    const ok = ROLES[b.ref].some(r => hit(r.res, res) && hit(r.verbs, verb));
    if (ok) return 'yes  via ' + b.kind + ' -> ' + b.ref;
  }
  return 'no   nothing matched, so denied';
}
for (const [who, verb, res, ns] of ASK) {
  console.log((who + ' ' + verb + ' ' + res + ' in ' + ns).padEnd(38), check(who, verb, res, ns));
}

চালালে এমন ফল আসে:

report-app list pods in shop           yes  via RoleBinding -> Role/pod-reader
report-app delete pods in shop         no   nothing matched, so denied
report-app list pods in bank           no   nothing matched, so denied
auditor get secrets in shop            no   nothing matched, so denied
auditor list namespaces in (cluster)   yes  via ClusterRoleBinding -> ClusterRole/ns-read
lazy-app get secrets in shop           yes  via RoleBinding -> Role/do-everything
lazy-app get secrets in bank           no   nothing matched, so denied

খেয়াল করুন, কোড শুধু yes খোঁজে। কোনো নিয়ম মিললেই অনুমতি, না মিললে শেষে “no”. এই কারণেই RBAC additive: একটি Binding অন্যটির অনুমতি কাড়তে পারে না। lazy-app shop-এ সব পারে, কিন্তু bank-এ কিছুই নয়, কারণ তার Binding একটি RoleBinding. এটিই RBAC-এর default deny.

cluster-admin আর wildcard এড়ান

“অনুমতি নেই” error এলে সবচেয়ে সহজ পথ হলো * বসিয়ে দেওয়া। সমস্যা সেরে যায়, আর least privilege শেষ হয়ে যায়। Namespace-এর ভেতরে একটি wildcard Role দিয়ে দেখি, কী কী খুলে গেল:

# LAZY=system:serviceaccount:abcl-kx:lazy-app
# Role do-everything: apiGroups ["*"], resources ["*"], verbs ["*"]
$ kubectl auth can-i get secrets -n abcl-kx --as=$LAZY
yes
$ kubectl auth can-i create rolebindings.rbac.authorization.k8s.io \
    -n abcl-kx --as=$LAZY
yes
$ kubectl auth can-i create pods/exec -n abcl-kx --as=$LAZY
yes
$ kubectl auth can-i get secrets -n default --as=$LAZY
no
$ kubectl auth can-i --list -n abcl-kx --as=$LAZY | head -2
Resources   Non-Resource URLs   Resource Names   Verbs
*.*         []                  []               [*]

একটি অ্যাপ এখন ওই namespace-এর সব Secret পড়তে পারে, নতুন অনুমতি বানাতে পারে, আর চলন্ত container-এ কমান্ড চালানোর অনুমতিও তার আছে। Wildcard আরও একটি বিপদ আনে: ভবিষ্যতে নতুন resource যোগ হলে সেটিও নিজে থেকে তালিকায় ঢুকে যায়। আর cluster-admin হলো এই ক্ষমতার cluster-জোড়া সংস্করণ। পরিচয় ফাঁস হলে তার blast radius পুরো cluster.

কিছু অনুমতি দেখতে নিরীহ, কিন্তু ক্ষমতা বাড়াতে পারে। পর্যালোচনায় এগুলো আলাদা করে দেখুন: Secret-এর get বা list, Pod-এর create (Pod সেই namespace-এর Secret আর ServiceAccount ব্যবহার করতে পারে), pods/exec, আর RBAC-এর bind, escalate ও impersonate verb. Kubernetes-এর নথিতে “RBAC good practices” অংশে এর তালিকা আছে।

তবে RBAC নিজেও একটি পাহারা রাখে। যাঁর কাছে Role বানানোর অনুমতি আছে, তিনি নিজের চেয়ে বেশি ক্ষমতার Role বানাতে পারেন না। auditor-কে শুধু Role তৈরির অনুমতি দিয়ে Secret পড়ার Role বানাতে বলা হলো:

$ kubectl create -f 15-try.yaml --dry-run=server --as=$AUD
Error from server (Forbidden): error when creating "15-try.yaml":
roles.rbac.authorization.k8s.io "secret-reader-try" is forbidden: user
"system:serviceaccount:abcl-kx:auditor" (groups=[…]) is attempting to
grant RBAC permissions not currently held:
{APIGroups:[""], Resources:["secrets"], Verbs:["get"]}

৪ · ServiceAccount আর স্বল্পমেয়াদি token

Pod-এর ভেতরের অ্যাপ API server-কে পরিচয় দেয় ServiceAccount-এর bearer token দিয়ে। অফিসের গেট পাসের কথা ভাবুন। পুরোনো ব্যবস্থায় পাসে কোনো তারিখ ছিল না, হারালে চিরকাল চলত। নতুন ব্যবস্থায় পাসে মেয়াদ, নাম আর কোন দর্শনার্থীর জন্য তা লেখা থাকে।

পুরোনো Kubernetes প্রতিটি ServiceAccount-এর জন্য Secret-এ মেয়াদহীন token বানিয়ে রাখত। এখন token আসে projected volume দিয়ে: kubelet TokenRequest API থেকে স্বল্পমেয়াদি token এনে ফাইলে রাখে আর সময়মতো বদলে দেয়। Kubernetes ১.২৪ থেকে নতুন ServiceAccount-এর জন্য মেয়াদহীন Secret আর নিজে থেকে তৈরি হয় না। আমাদের run-এও তাই দেখা গেছে: report-app বানানোর পরও namespace-এ কোনো Secret ছিল না।

# projected volume on the Pod (added by admission)
$ kubectl get pod hardened -n abcl-kx -o json
…
  "name": "kube-api-access-x8rks"
  "serviceAccountToken": { "expirationSeconds": 3607, "path": "token" }
…
  "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
  "readOnly": true

# claims decoded inside the Pod with a small Python script.
# Only claims are printed, never the token itself.
lifetime_seconds 31536000
aud ['https://kubernetes.default.svc.cluster.local']
sub system:serviceaccount:abcl-kx:report-app
pod hardened warnafter 3607

এখানে একটি সত্যি অবাক করার জিনিস আছে। Pod চেয়েছে ৩৬০৭ সেকেন্ড, অর্থাৎ প্রায় ১ ঘণ্টা। কিন্তু token-এর ভেতরের মেয়াদ দাঁড়াল ৩১৫৩৬০০০ সেকেন্ড, মানে ঠিক ৩৬৫ দিন! কারণ পুরোনো client-দের ভাঙা ঠেকাতে API server-এর --service-account-extend-token-expiration সেটিং ডিফল্টে চালু থাকে। ৩৬০৭ সংখ্যাটি সেই বিশেষ অনুরোধ চিহ্নিত করে। warnafter জানায় ৩৬০৭ সেকেন্ড পরের ব্যবহার সতর্কতা হিসেবে ধরা হবে। এই cluster-এর flag-এ সেটিং বদলানো হয়নি, আর kubectl create token --duration=10m দিয়ে আলাদা বানানো token-এর মেয়াদ ছিল ঠিক ৬০০ সেকেন্ড।

শিক্ষা হলো, “token স্বল্পমেয়াদি” কথাটি ধরে না নিয়ে নিজের cluster-এ মেপে দেখুন। নথি অনুযায়ী token-এর বয়স মোট মেয়াদের ৮০% বা ২৪ ঘণ্টা পেরোলে kubelet ফাইলটি আগেভাগে বদলে দেয়, তাই অ্যাপকে ফাইল থেকে আবার পড়তে হবে।

এই token-এর আসল শক্তি হলো Pod-এর সাথে বাঁধা থাকা। Pod মুছলে token বাতিল। সেটিও মেপে দেখা হলো। Token কখনো পর্দায় আসেনি, একটি অস্থায়ী kubeconfig-এ রেখে পরে মুছে ফেলা হয়েছে:

$ kubectl create token report-app -n abcl-kx --duration=10m \
    --bound-object-kind=Pod --bound-object-name=hardened \
    --bound-object-uid=…          # stored, never printed
$ kubectl --kubeconfig t.kubeconfig get pods -n abcl-kx
NAME       READY   STATUS    RESTARTS   AGE
hardened   1/1     Running   0          2s
$ kubectl delete pod hardened -n abcl-kx --grace-period=1
pod "hardened" deleted from abcl-kx namespace

# same token, right after the delete and about 4 seconds later
No resources found in abcl-kx namespace.
No resources found in abcl-kx namespace.
# same token, about 12 seconds later
error: You must be logged in to the server (Unauthorized)

Token বাতিল হয়েছে, তবে সাথে সাথে নয়। সম্ভবত API server সফল যাচাইয়ের ফল অল্প সময় cache রাখে, তাই কয়েক সেকেন্ডের ফাঁক থাকে। এই সময় আপনার cluster-এ আলাদা হতে পারে। প্রথম আউটপুটটিও লক্ষ করুন: এই token Pod দেখতে পারছে, কারণ RBAC-এ report-app-এর সেই অনুমতি আছে। Token পরিচয় দেয়, অনুমতি দেয় না।

যে অ্যাপ API server-এর সাথে কথাই বলে না, তার token দরকার নেই। Pod-এর spec-এ automountServiceAccountToken: false লিখে দিন। প্রতিটি অ্যাপের জন্য আলাদা ServiceAccount রাখুন, default ব্যবহার করবেন না।

৫ · NetworkPolicy — আর সবচেয়ে জরুরি পরীক্ষাটি

ডিফল্টে Kubernetes-এর network সমতল: যেকোনো Pod যেকোনো Pod-এর সাথে কথা বলতে পারে, এমনকি অন্য namespace-এরও। অর্থাৎ একটি Pod দখল হলে ডেটাবেস পর্যন্ত রাস্তা খোলা। NetworkPolicy Pod-এর জন্য ঠিক সেই কাজ করে, যা পর্ব ২৭-এ security group করেছে, তবে IP নয়, label দেখে।

নিয়মমানে
কোনো policy Pod-কে বাছেনিPod উন্মুক্ত, সব traffic চলে
অন্তত একটি policy Pod-কে বেছেছেসেই দিকের traffic বন্ধ, যা allow করা হয়েছে শুধু তা চলে
একাধিক policyAllow-গুলো যোগ হয়, deny নিয়ম নেই
podSelector: {}Namespace-এর সব Pod
দুই Pod-এর সংযোগউৎসের egress আর গন্তব্যের ingress, দুটোই অনুমতি দিতে হবে

প্রথমে সব বন্ধ, তারপর একটি পথ খোলা হলো:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: abcl-kx
spec:
  podSelector: {}
  policyTypes: ["Ingress"]
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-web
  namespace: abcl-kx
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: frontend
      ports:
        - protocol: TCP
          port: 80

দুটি manifest-ই --dry-run=server পাস করেছে। ধরে নেওয়া সহজ, এখন guest Pod আর web-এ পৌঁছাতে পারবে না। কিন্তু ধরে না নিয়ে মাপা হলো। frontend, guest আর web Pod চালিয়ে policy দেওয়ার আগে ও পরে সংযোগ পরীক্ষা করা হলো:

# each result line: kubectl exec POD -- wget -q -O /dev/null -T 3 http://web
# exit 0 means the request succeeded
# before any policy
frontend -> web: exit=0
guest    -> web: exit=0

$ kubectl apply -f 21-deny.yaml
networkpolicy.networking.k8s.io/default-deny-ingress created
frontend -> web: exit=0
guest    -> web: exit=0

$ kubectl apply -f 22-allow.yaml
networkpolicy.networking.k8s.io/allow-frontend-to-web created
frontend -> web: exit=0
guest    -> web: exit=0

$ kubectl get netpol -n abcl-kx
NAME                    POD-SELECTOR   AGE
allow-frontend-to-web   app=web        1s
default-deny-ingress    <none>         5s

$ kubectl exec -n abcl-kx guest -- wget -q -O /dev/null -T 3 http://web
$ echo "exit code: $?"
exit code: 0

সত্যি ফল: এই cluster-এ NetworkPolicy প্রয়োগ হয়নি। Policy দুটি API server গ্রহণ করেছে, get netpol-এ দেখাচ্ছে, কিন্তু guest তখনও web-এ পৌঁছেছে। Pod-এর IP দিয়ে সরাসরি চেষ্টাতেও একই ফল। কোনো error নেই, কোনো warning নেই। এই cluster-এর kube-system-এ policy প্রয়োগ করার মতো কোনো network agent দেখা যায়নি।

বাড়ির গেটে “অপরিচিতদের প্রবেশ নিষেধ” তালিকা ঝুলছে, কিন্তু গেটে কোনো দারোয়ান নেই। তালিকাটি ঠিকই আছে, কেউ পড়ছে না। NetworkPolicy সেই তালিকা, আর CNI plugin সেই দারোয়ান।

Kubernetes-এর API শুধু policy জমা রাখে। প্রয়োগ করার কাজ network plugin-এর। Calico আর Cilium-এর মতো plugin NetworkPolicy প্রয়োগ করে। আবার কিছু plugin একা করে না, যেমন সাধারণ Flannel. Managed cluster-এ এটি প্রায়ই একটি বাড়তি সেটিং, যা provider আর সময়ভেদে বদলায়। তাই নিয়ম একটাই: নিজের cluster-এ একটি নিষিদ্ধ সংযোগ চেষ্টা করে দেখুন যে সেটি সত্যিই ব্যর্থ হয়।

নিচের ধাপগুলোর ১ থেকে ৪ নম্বর হলো, enforcement আছে এমন plugin-এ যা হওয়ার কথা, আর সেই হিসাব আসে নিচের সিমুলেটর থেকে। শেষ ধাপটি আমাদের আসল run. ডেটাবেস পরীক্ষাটি আসল run-এ করা হয়নি, তাই সেখানে — আছে।

কে কার কাছে পৌঁছায়
উৎস
frontend → web
guest → web
web → db
frontend → db
Policyকিছুই নেই
$ kubectl get netpol
No resources found
Policy যোগসব Pod বাছা হলো
default-deny-ingress
  podSelector: {}
  policyTypes: [Ingress]
Policy যোগএকটি পথ
allow-frontend-to-web
  podSelector: app=web
  from: role=frontend   port: 80
Policy যোগদ্বিতীয় পথ
web-to-db
  podSelector: app=db
  from: app=web
আসল clusterEnforcement নেই
default-deny-ingress    created
allow-frontend-to-web   created
guest -> web: exit=0
  1. কোনো policy নেই। কোনো Pod বাছা হয়নি, তাই চারটি পথই খোলা। guest-ও web-এ পৌঁছায়।
  2. Default deny. podSelector: {} সব Pod বেছে নিল, আর allow নিয়ম নেই। তাই চারটি পথই বন্ধ। Production-এ এই ধাপ হঠাৎ দিলে চলন্ত সার্ভিস থেমে যাবে।
  3. একটি পথ খোলা। web-এ role=frontend ঢুকতে পারে। frontend → web ok, কিন্তু guest → web এখনো BLOCK, আর web → db-ও বন্ধ।
  4. দ্বিতীয় পথ। db-তে শুধু app=web. এখন web → db ok, কিন্তু frontend → db BLOCK. স্তর ডিঙানো গেল না।
  5. এবার আসল cluster. একই ধরনের policy জমা হলো, কিন্তু guest → web ok রয়ে গেল। ফলে policy লেখা আর policy কাজ করা দুটি আলাদা জিনিস।

নিজে policy বদলে ম্যাট্রিক্স দেখুন। একই কোড দুইভাবে চলে: plugin প্রয়োগ করলে, আর না করলে।

// NetworkPolicy reachability matrix (ingress only). Edit POLICIES and run again.
const PODS = {
  frontend: { role: 'frontend' }, guest: { role: 'guest' },
  web: { app: 'web' }, db: { app: 'db' },
};
const POLICIES = [
  { name: 'default-deny', select: {}, from: [] },
  { name: 'frontend-to-web', select: { app: 'web' }, from: [{ role: 'frontend' }] },
  { name: 'web-to-db', select: { app: 'db' }, from: [{ app: 'web' }] },
];
// An empty selector {} matches every Pod.
const matches = (sel, labels) => Object.keys(sel).every(k => labels[k] === sel[k]);

function allowed(src, dst, enforced) {
  if (!enforced) return true;                // the plugin ignores every policy
  const mine = POLICIES.filter(p => matches(p.select, PODS[dst]));
  if (mine.length === 0) return true;        // not selected: not isolated
  return mine.some(p => p.from.some(f => matches(f, PODS[src]))); // union
}
const names = Object.keys(PODS);
for (const enforced of [true, false]) {
  console.log(enforced ? 'CNI enforces NetworkPolicy:' : 'CNI ignores NetworkPolicy:');
  console.log('src/dst'.padEnd(10) + names.map(n => n.padEnd(10)).join(''));
  for (const src of names) {
    const row = names.map(d => (src === d ? '-' : allowed(src, d, enforced) ? 'ok' : 'BLOCK').padEnd(10));
    console.log(src.padEnd(10) + row.join(''));
  }
  console.log('');
}

চালালে এমন ফল আসে:

CNI enforces NetworkPolicy:
src/dst   frontend  guest     web       db        
frontend  -         BLOCK     ok        BLOCK     
guest     BLOCK     -         BLOCK     BLOCK     
web       BLOCK     BLOCK     -         ok        
db        BLOCK     BLOCK     BLOCK     -         

CNI ignores NetworkPolicy:
src/dst   frontend  guest     web       db        
frontend  -         ok        ok        ok        
guest     ok        -         ok        ok        
web       ok        ok        -         ok        
db        ok        ok        ok        -         

ওপরের ছকে শুধু দুটি ok. নিচের ছকটি আমাদের cluster-এর মতো, সব ok. mine.length === 0 লাইনটি দেখুন: যে Pod-কে কোনো policy বাছেনি, সে উন্মুক্ত থাকে। default-deny সরিয়ে চালিয়ে দেখুন, frontend আর guest আবার সবার কাছ থেকে traffic পাবে।

Egress-এও default deny দিলে DNS-ও বন্ধ হয়ে যায়, আর Service-এর নাম আর খোঁজা যায় না। তাই সাধারণত kube-system-এর DNS-এর দিকে port ৫৩ allow করতে হয়। আর নথি অনুযায়ী, node থেকে তার নিজের Pod-এ আসা traffic, যেমন kubelet-এর probe, NetworkPolicy দিয়ে বন্ধ হয় না।

৬ · Pod Security Standards আর Pod Security Admission

RBAC বলে কে Pod বানাতে পারবে। কিন্তু কেমন Pod বানাতে পারবে, তা বলে না। Node-এ পূর্ণ ক্ষমতা নিয়ে চলা Pod বানানোর সুযোগ থাকলে namespace-এর সীমা প্রায় অর্থহীন হয়ে যায়। এই ফাঁক পূরণ করে Pod Security Standards: তিনটি নির্দিষ্ট স্তর।

স্তরকী করেকার জন্য
privilegedকোনো বাধা নেইNode-স্তরের বিশ্বস্ত system component
baselineপরিচিত ক্ষমতা-বৃদ্ধি আটকায়: privileged, hostNetwork, hostPath, বাড়তি capabilityসাধারণ অ্যাপের ন্যূনতম স্তর
restrictedBaseline-এর সব, সাথে non-root, capability বাদ, seccompনিরাপত্তা-সংবেদনশীল অ্যাপ

এই স্তর প্রয়োগ করে built-in controller Pod Security Admission (PSA). এটি namespace-এর label পড়ে, আর তিনটি mode-এ কাজ করে: enforce Pod বাতিল করে, warn সতর্ক করে, audit audit log-এ চিহ্ন রাখে।

# preview on a namespace that has running Pods: nothing changes
$ kubectl label --dry-run=server --overwrite ns abcl-kx \
    pod-security.kubernetes.io/enforce=restricted
Warning: existing pods in namespace "abcl-kx" violate the new
PodSecurity enforce level "restricted:latest"
Warning: frontend (and 2 other pods): allowPrivilegeEscalation != false,
unrestricted capabilities, runAsNonRoot != true, seccompProfile
namespace/abcl-kx labeled (server dry run)

# baseline enforced, restricted only warned and audited
$ kubectl label ns abcl-kx pod-security.kubernetes.io/enforce=baseline \
    pod-security.kubernetes.io/warn=restricted \
    pod-security.kubernetes.io/audit=restricted --overwrite
namespace/abcl-kx labeled

# privileged Pod, server dry run only: it never runs
$ kubectl apply --dry-run=server -f 30-privileged.yaml
Error from server (Forbidden): error when creating "30-privileged.yaml":
pods "too-powerful" is forbidden: violates PodSecurity "baseline:latest":
privileged (container "app" must not set securityContext.privileged=true)

# a plain Pod passes baseline, but restricted warns
$ kubectl apply --dry-run=server -f 31-plain.yaml
Warning: would violate PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (…), unrestricted capabilities (…),
runAsNonRoot != true (…), seccompProfile (…)
pod/plain-nginx created (server dry run)

# restricted enforced, pinned to one version, a real request
$ kubectl label ns abcl-kx pod-security.kubernetes.io/enforce=restricted \
    pod-security.kubernetes.io/enforce-version=v1.34 --overwrite
namespace/abcl-kx labeled
$ kubectl apply -f 31-plain.yaml
Error from server (Forbidden): error when creating "31-plain.yaml":
pods "plain-nginx" is forbidden: violates PodSecurity "restricted:v1.34":
allowPrivilegeEscalation != false (container "nginx" must set
securityContext.allowPrivilegeEscalation=false), unrestricted capabilities
(container "nginx" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "nginx" must set
securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx"
must set securityContext.seccompProfile.type to "RuntimeDefault" or
"Localhost")
$ kubectl get pods -n abcl-kx
No resources found in abcl-kx namespace.

মেসেজগুলো পড়লেই সমাধান পাওয়া যায়, কারণ প্রতিটি লঙ্ঘনের সাথে ঠিক কোন field বসাতে হবে তা লেখা আছে। প্রথম Warning দুটি বিশেষ কাজের: label বসানোর আগেই জানা গেল, তিনটি চলন্ত Pod নিয়ম ভাঙছে। তবে enforce চালু হলেও চলন্ত Pod বন্ধ হয় না, PSA শুধু নতুন অনুরোধ দেখে। enforce-version নির্দিষ্ট version-এর নিয়মে বাঁধে, latest upgrade-এর সাথে বদলাতে পারে।

নিরাপদ চালুর ক্রম হলো: আগে warn আর audit, লঙ্ঘন শূন্যে আনা, তারপর enforce. একটি namespace-এ পরিষ্কার করে পরেরটিতে যান।

আগের ব্যবস্থার নাম ছিল PodSecurityPolicy (PSP). এটি Kubernetes ১.২১-এ deprecated হয় আর ১.২৫-এ সরানো হয়। একই version-এ Pod Security Admission স্থিতিশীল হয়। পুরোনো টিউটোরিয়াল বা AI যদি kind: PodSecurityPolicy দেয়, আধুনিক cluster সেটি চিনবেই না।

৭ · securityContext — Pod শক্ত করা, লাইন ধরে

Restricted-এর দাবি মেটানো হয় Pod-এর securityContext-এ। প্রতিটি লাইন একটি নির্দিষ্ট ঝুঁকি কমায়। python:3.12-alpine দিয়ে একটি ছোট HTTP সার্ভার ধাপে ধাপে শক্ত করা যাক। শুরুতে এর চারটি লঙ্ঘন, যা plain-nginx-এর মতোই।

1spec:2  securityContext:3    runAsNonRoot: true4    runAsUser: 10005    seccompProfile:6      type: RuntimeDefault7  containers:8    - name: app9      image: python:3.12-alpine10      securityContext:11        allowPrivilegeEscalation: false12        capabilities:13          drop: ["ALL"]14        readOnlyRootFilesystem: true15      volumeMounts:16        - {name: tmp, mountPath: /tmp}17  volumes:18    - {name: tmp, emptyDir: {}}
Restricted যাচাই
লঙ্ঘন
Admission
চলবে যে user-এ
Capability
Root filesystem
  1. শুরু। নিরাপত্তার কোনো লাইন ছাড়া Pod-টির লঙ্ঘন 4. Image-এর ডিফল্ট user root, তাই container-এর ভেতরে process root হিসেবে চলবে।
  2. runAsNonRoot: true. লঙ্ঘন 3. কিন্তু এই লাইন user বদলায় না, শুধু দাবি করে। Image root চাইলে kubelet container চালু করবে না।
  3. runAsUser: 1000. এবার user ঠিক হলো 1000. এটি restricted-এর লঙ্ঘন গণনা বদলায় না, তাই সংখ্যা 3-ই থাকল, কিন্তু আগের ধাপের চালু না হওয়ার সমস্যা মিটল।
  4. seccompProfile. Seccomp kernel-এর system call-এর তালিকা ছোট করে। RuntimeDefault runtime-এর ডিফল্ট profile. লঙ্ঘন 2.
  5. allowPrivilegeEscalation: false. Process নিজের ক্ষমতা আর বাড়াতে পারবে না, যা no-new-privileges বিট চালু করে। লঙ্ঘন 1.
  6. drop: ["ALL"]. সব Linux capability বাদ। লঙ্ঘন 0, admission এখন পাস. দরকার হলে শুধু NET_BIND_SERVICE ফিরিয়ে আনা যায়।
  7. Read-only root filesystem. এটি restricted-এর অংশ নয়, তাই লঙ্ঘন 0-ই। তবুও মূল্যবান: দখলকারী বাইনারি লিখে রাখতে পারবে না। যেখানে লেখা দরকার, সেখানে emptyDir.

কাগজে ঠিক থাকলেই হবে না। চলন্ত Pod-এর ভেতরে দেখা যাক প্রতিটি সেটিং সত্যিই কাজ করছে কি না:

apiVersion: v1
kind: Pod
metadata:
  name: hardened
  namespace: abcl-kx
spec:
  serviceAccountName: report-app
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: python:3.12-alpine
      imagePullPolicy: IfNotPresent
      command: ["python", "-m", "http.server", "8080", "--directory", "/tmp"]
      ports:
        - containerPort: 8080
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      resources:
        requests: {cpu: 20m, memory: 32Mi}
        limits: {cpu: 100m, memory: 64Mi}
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir:
        sizeLimit: 8Mi
$ kubectl apply -f 32-hardened.yaml
pod/hardened created
$ kubectl exec -n abcl-kx hardened -- id
uid=1000 gid=1000 groups=1000
$ kubectl exec -n abcl-kx hardened -- sh -c 'touch /etc/x; echo "touch rc=$?"
    touch /tmp/ok && echo tmp-ok
    grep -E "^(CapEff|CapBnd|NoNewPrivs|Seccomp):" /proc/self/status'
touch rc=1
touch: /etc/x: Read-only file system
tmp-ok
CapEff:	0000000000000000
CapBnd:	0000000000000000
NoNewPrivs:	1
Seccomp:	2

# restricted allows runAsNonRoot without a runAsUser, then this happens:
$ kubectl apply -f 33-rootimage.yaml      # nginx image, runs as root
pod/root-image created
$ kubectl get pod root-image -n abcl-kx
NAME         READY   STATUS                       RESTARTS   AGE
root-image   0/1     CreateContainerConfigError   0          …
$ kubectl describe pod root-image -n abcl-kx | grep Error:
… kubelet  Error: container has runAsNonRoot and image will run as root (…)

প্রতিটি লাইনের প্রমাণ মিলল। uid=1000 মানে root নয়। /etc-এ লেখা ব্যর্থ, /tmp-এর emptyDir-এ সফল। CapEff সব শূন্য, NoNewPrivs: 1, আর Seccomp: 2 মানে filter মোড চালু। শেষ অংশটিই সবচেয়ে শিক্ষণীয়: root-image admission পেরিয়ে গেছে, কারণ YAML-এ দাবিটি ঠিক ছিল। কিন্তু image-এর user মিলিয়ে দেখা admission-এর সাধ্যের বাইরে, তাই আটকেছে kubelet. একে বলে দুই স্তরের যাচাই।

বড় manifest-এ চোখে দেখে নিয়ম মেলানো কঠিন। নিচের সিমুলেটর restricted-এর একটি ছোট অংশ যাচাই করে। PLAIN বা RISKY বদলে দেখুন। এটি শেখার জন্য, আসল PSA-র সম্পূর্ণ তালিকা নয়।

// Small part of the "restricted" Pod Security profile. Edit a spec and run again.
const PLAIN = { pod: {}, ctr: {}, volumes: ['configMap'] };
const HARDENED = {
  pod: { runAsNonRoot: true, runAsUser: 1000, seccomp: 'RuntimeDefault' },
  ctr: { allowPrivilegeEscalation: false, drop: ['ALL'], readOnlyRootFilesystem: true },
  volumes: ['emptyDir', 'projected'],
};
const RISKY = {
  pod: { hostNetwork: true }, ctr: { privileged: true, add: ['SYS_ADMIN'] },
  volumes: ['hostPath'],
};
const OK_VOLUMES = ['configMap', 'csi', 'downwardAPI', 'emptyDir', 'ephemeral',
  'persistentVolumeClaim', 'projected', 'secret'];
const CHECKS = [
  ['privileged', s => !s.ctr.privileged],
  ['hostNetwork', s => !s.pod.hostNetwork],
  ['allowPrivilegeEscalation=false', s => s.ctr.allowPrivilegeEscalation === false],
  ['capabilities.drop=ALL', s => (s.ctr.drop || []).includes('ALL')],
  ['capabilities.add', s => (s.ctr.add || []).every(c => c === 'NET_BIND_SERVICE')],
  ['runAsNonRoot=true', s => s.pod.runAsNonRoot === true],
  ['runAsUser not 0', s => s.pod.runAsUser !== 0],
  ['seccomp', s => ['RuntimeDefault', 'Localhost'].includes(s.pod.seccomp)],
  ['volume types', s => s.volumes.every(v => OK_VOLUMES.includes(v))],
];
function report(name, spec) {
  const bad = CHECKS.filter(([, ok]) => !ok(spec)).map(([label]) => label);
  console.log(name + ': ' + (bad.length ? 'REJECT, ' + bad.length + ' violation(s)' : 'PASS'));
  for (const b of bad) console.log('   - ' + b);
  if (!spec.ctr.readOnlyRootFilesystem) {
    console.log('   note: root filesystem is writable (allowed by restricted)');
  }
}
report('PLAIN', PLAIN);
report('HARDENED', HARDENED);
report('RISKY', RISKY);

চালালে এমন ফল আসে:

PLAIN: REJECT, 4 violation(s)
   - allowPrivilegeEscalation=false
   - capabilities.drop=ALL
   - runAsNonRoot=true
   - seccomp
   note: root filesystem is writable (allowed by restricted)
HARDENED: PASS
RISKY: REJECT, 8 violation(s)
   - privileged
   - hostNetwork
   - allowPrivilegeEscalation=false
   - capabilities.drop=ALL
   - capabilities.add
   - runAsNonRoot=true
   - seccomp
   - volume types
   note: root filesystem is writable (allowed by restricted)

PLAIN-এর চারটি লঙ্ঘন আসল PSA-র মেসেজের সাথে মিলে যায়। HARDENED পাস। RISKY-তে আটটি। আর note লাইনটি মনে করিয়ে দেয়, readOnlyRootFilesystem বাধ্যতামূলক নয়, এটি আপনার বাড়তি স্তর।

৮ · Admission control আর policy engine

PSA-র নিয়ম নির্দিষ্ট। কিন্তু প্রতিষ্ঠানের নিজের নিয়ম থাকে: latest tag চলবে না, image শুধু নিজেদের registry থেকে, প্রতিটি Deployment-এ team label লাগবে। এগুলোই policy as code.

ব্যবস্থানিয়ম লেখার ভাষাকোথায় চলেকখন
ValidatingAdmissionPolicyCELAPI server-এর ভেতরেসরল যাচাই, বাড়তি কিছু না চালিয়ে
KyvernoYAML-ধাঁচের policyআলাদা webhookযাচাই, বদল, স্বাক্ষর পরীক্ষা, Kubernetes-কেন্দ্রিক দল
OPA GatekeeperRegoআলাদা webhookজটিল নিয়ম, Kubernetes-এর বাইরেও OPA ব্যবহার করে এমন দল

ValidatingAdmissionPolicy Kubernetes ১.৩০ থেকে স্থিতিশীল, আর এই cluster-এ admissionregistration.k8s.io/v1 দিয়ে চলে। বাইরের কিছু ইনস্টল না করেই একটি নিয়ম বসানো হলো, যা শুধু আমাদের namespace-এ কাজ করে:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: abcl-kx-no-latest-tag
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  validations:
    - expression: "object.spec.containers.all(c, c.image.contains(':') && !c.image.endsWith(':latest'))"
      message: "image must carry an explicit tag, and that tag must not be latest"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: abcl-kx-no-latest-tag
spec:
  policyName: abcl-kx-no-latest-tag
  validationActions: ["Deny"]
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: abcl-kx
$ kubectl apply --dry-run=server -f 41-latest.yaml    # image: python:latest
The pods "lazy-tag" is invalid: : ValidatingAdmissionPolicy
'abcl-kx-no-latest-tag' with binding 'abcl-kx-no-latest-tag' denied request:
image must carry an explicit tag, and that tag must not be latest
$ kubectl apply --dry-run=server -f 42-good.yaml      # python:3.12-alpine
pod/good-tag created (server dry run)

একই Pod-এর শুধু image আলাদা, ফলও আলাদা। Policy আর Binding আলাদা বস্তু, ঠিক Role আর RoleBinding-এর মতো: নিয়ম এক জায়গায়, কোথায় খাটবে আরেক জায়গায়। namespaceSelector-এর জন্য অন্য namespace-এর ওপর এটি কোনো প্রভাব ফেলেনি। পরীক্ষা শেষে দুটিই নাম ধরে মুছে ফেলা হয়েছে। মনে রাখবেন, এই CEL নিয়মটি শুধু শিক্ষার জন্য সরল: registry-র port বা initContainer সে দেখে না।

Webhook-ভিত্তিক policy নিজেই ঝুঁকি। failurePolicy: Fail থাকলে webhook নষ্ট হলে নতুন Pod আটকে যেতে পারে, Ignore থাকলে নিয়ম নীরবে বন্ধ হয়। তাই webhook-এর replica, timeout আর kube-system বাদ রাখার বিষয়টি পরিকল্পনা করুন।

Image স্বাক্ষর: পর্ব ৪১-এ cosign দিয়ে image-এ সই করা দেখেছেন। Kubernetes নিজে সেই সই যাচাই করে না। যাচাই হয় admission-এ, Kyverno-র image verification বা Sigstore-এর policy-controller-এর মতো ব্যবস্থায়। ধারণাটি হলো, যে digest-এ বিশ্বস্ত সই নেই, সেটি cluster-এ ঢুকবে না। সাথে tag নয়, digest দিয়ে image ধরা ভালো, কারণ tag বদলানো যায়।

৯ · Secret, audit, cloud পরিচয় আর runtime নজরদারি

Secret আর etcd encryption

Kubernetes Secret ডিফল্টে শুধু base64, এটি encryption নয়। আর etcd-তে তা encrypt করে রাখতে API server-এ --encryption-provider-config লাগে। আমাদের cluster-এ এই flag নেই। বিস্তারিত দেখেছেন পর্ব ৪৭-এ (ConfigMap, Secret আর storage), আর KMS-এর ভিত্তি পর্ব ৩৩-এ। সাথে RBAC-এ Secret-এর get আর list কড়াভাবে সীমিত রাখুন।

Audit logging

“কে কখন এই Role বদলালেন?” এর উত্তর দেয় Kubernetes-এর audit log. API server প্রতিটি অনুরোধের রেকর্ড রাখতে পারে, তবে policy file না দিলে চালু হয় না। আমাদের cluster-এ --audit- flag নেই, ফলে PSA-র audit label সেখানে দেখার মতো কোনো রেকর্ড রাখে না। স্তর চারটি: None, Metadata, Request, RequestResponse. Secret-এর জন্য Metadata-র বেশি নয়, নইলে গোপন মান log-এ চলে যাবে। Managed cluster-এ control-plane log প্রায়ই আলাদা করে চালু করতে হয়, আর log রাখার খরচ আছে।

Workload identity — cloud IAM-এ পরিচয় দেওয়া

Pod-এর যদি S3 বা Cloud Storage লাগে, access key Secret-এ রাখা পুরোনো ঝুঁকি। ভালো পথ workload identity: ServiceAccount-এর স্বাক্ষরিত token cloud-এর কাছে দেখিয়ে স্বল্পমেয়াদি temporary credential নেওয়া। এটি পর্ব ৩১-এর identity federation-এর Kubernetes রূপ। can-i --list-এর পুরো আউটপুটে /.well-known/openid-configuration নামে একটি পথ থাকে। Cluster-এর সেই OIDC তথ্যের ওপরই এটি দাঁড়িয়ে। উদাহরণ হলো AWS-এর IRSA ও EKS Pod Identity, Google Cloud-এর GKE Workload Identity Federation, আর Azure-এর Microsoft Entra Workload ID. নাম আর সেটআপ provider-ভেদে বদলায়।

Runtime detection আর CIS Benchmark

আগের সব স্তর চালুর আগের পাহারা। চলার সময় কিছু ঘটলে? Falco-র মতো টুল eBPF ইত্যাদি দিয়ে kernel-এর ঘটনা দেখে নিয়মের সাথে মেলায়, যেমন container-এ হঠাৎ shell খোলা বা সংবেদনশীল ফাইল পড়া। এটি সতর্ক করে, নিজে থেকে আটকায় না। সতর্কবার্তা না দেখলে এর মূল্য নেই। আর পুরো cluster-এর সেটিং মিলিয়ে দেখতে CIS Kubernetes Benchmark আছে, যা kube-bench ধরনের টুল দিয়ে যাচাই করা যায়। Managed cluster-এ provider-নির্দিষ্ট সংস্করণ দেখুন, কারণ control plane আপনার হাতে নেই।

আরও গভীরে: RBAC-এর অনুমতি একত্র করা হয় বলে API server-কে শুধু খুঁজতে হয়, কোনো ক্রম মেলাতে হয় না। Authorization chain-এ একাধিক module থাকলে প্রথম স্পষ্ট সিদ্ধান্তটিই চূড়ান্ত, আর webhook authorizer ইচ্ছা করলে deny-ও ফেরাতে পারে। NetworkPolicy-র ওপরেও বিকল্প আছে: Cilium-এর L7 নিয়ম আর service mesh-এর mTLS, যা IP নয়, পরিচয় ধরে যাচাই করে। এটি zero trust-এর দিকে এগোয়।

পরীক্ষা ও ইন্টারভিউতে:

  • ৪০১ আর ৪০৩-এর পার্থক্য? ৪০১ মানে পরিচয় গৃহীত হয়নি, ৪০৩ মানে পরিচয় মিলেছে কিন্তু অনুমতি নেই।
  • Role আর ClusterRole? Role এক namespace-এর; ClusterRole namespace-হীন, RoleBinding দিয়ে এক namespace-এও ব্যবহার করা যায়।
  • NetworkPolicy দিলাম, traffic আটকায়নি কেন? সম্ভবত CNI plugin সেটি প্রয়োগ করে না। নিষিদ্ধ সংযোগ চেষ্টা করে যাচাই করতে হবে।
  • PSP-র বদলে কী? Pod Security Admission, আর জটিল নিয়মের জন্য policy engine. PSP ১.২৫-এ সরানো হয়েছে।
  • --dry-run=server আর client-এর পার্থক্য? Server dry run authorization আর admission পেরোয়, শুধু etcd-তে লেখে না; client dry run শুধু স্থানীয়ভাবে দেখে।

১০ · AI-এর যুগে Kubernetes নিরাপত্তা

AI manifest দ্রুত লেখে, কিন্তু সে আপনার cluster দেখে না। আর “কাজ করছে” বানাতে গিয়ে প্রায়ই নিরাপত্তা খুলে দেয়। এই পর্বের প্রায় প্রতিটি ফলাফল YAML পড়ে নয়, পরীক্ষা করে জানা গেছে। AI-এর কথাও একইভাবে যাচাই করুন।

AI-এর ভুলকেন বিপদযাচাই
cluster-admin বা verbs: ["*"] দেয়একটি Pod দখল হলে পুরো clustercan-i --list --as
NetworkPolicy লিখে বলে “এখন নিরাপদ”CNI প্রয়োগ না করলে কিছুই হয় নানিষিদ্ধ সংযোগ চেষ্টা
kind: PodSecurityPolicy বা পুরোনো apiVersion১.২৫ থেকে নেই; নিরাপত্তা ধরে নেওয়া ভুলkubectl api-resources, --dry-run=server
Error সারাতে privileged: true বা rootNode-এর সীমা দুর্বল হয়PSA label, আসল কারণ খোঁজা
image: …:latest, Secret YAML-এ সরাসরি মানঅজানা version, secret ফাঁসPolicy, secret scanner
অস্তিত্বহীন field বা flag বানায়নিয়মটি হয়তো নীরবে উপেক্ষিতkubectl explain, --validate=strict
kubectl delete সতর্কতা ছাড়া দেয়ভুল namespace-এ ক্ষতি--dry-run=server, context দেখা

AI-কে কাজ দিন, কিন্তু সীমা আর প্রমাণ দুটোই চেয়ে নিন। Cluster-এর আসল নাম, kubeconfig বা token কখনো prompt-এ দেবেন না।

একটি কাল্পনিক অনলাইন বাজার অ্যাপের জন্য Kubernetes ১.৩৪-এ manifest লিখে দাও। Namespace: shop. অ্যাপটি শুধু ConfigMap পড়ে আর port 8080-এ চলে। শর্ত: ১. আলাদা ServiceAccount, least privilege Role আর RoleBinding. কোনো wildcard, cluster-admin বা ClusterRoleBinding নয়। ২. Pod যেন Pod Security restricted পাস করে, সাথে readOnlyRootFilesystem. ৩. NetworkPolicy: default deny, শুধু role=frontend থেকে port 8080. ধরে নিও না আমার CNI এটি প্রয়োগ করে। ৪. প্রতিটি field-এর পাশে কেন আছে তা এক লাইনে বলো। Kubernetes নথিতে নেই এমন field লিখো না। ৫. শেষে যাচাইয়ের কমান্ড দাও: --dry-run=server, kubectl auth can-i-এর হ্যাঁ ও না পরীক্ষা, আর নিষিদ্ধ সংযোগের পরীক্ষা। মুছে ফেলার কোনো কমান্ড দিও না।

AI-এর লেখা manifest হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১RBAC-এ deny নিয়ম নেই কেন? Cloud IAM-এ তো explicit deny আছে।

Kubernetes-এর শুরুর দিকে authorization ছিল ABAC দিয়ে: API server-এর একটি ফাইলে নিয়ম লেখা, আর বদলালে API server আবার চালু করা। Cluster বড় হলে এটি চালানো কঠিন হয়ে পড়ে। RBAC ১.৬-এ beta আর ১.৮-এ স্থিতিশীল হয়, আর নিয়মগুলো হয় সাধারণ API বস্তু।

নকশায় ইচ্ছা করেই শুধু additive অনুমতি রাখা হয়েছে। এর সুবিধা অনেক। ক্রম নিয়ে কোনো ভাবনা নেই, কারণ দুটি নিয়মের সংঘাত হতেই পারে না। একটি নিয়ম যোগ করলে আগের কারো ক্ষমতা হঠাৎ চলে যায় না, যাতে চলন্ত controller ভেঙে না পড়ে। যাচাইও সরল: একটি মিল পেলেই শেষ। পর্ব ৩২-এর IAM-এ explicit deny, permission boundary আর organization policy একসাথে হিসাব করতে হয়, যা শক্তিশালী কিন্তু বোঝা কঠিন।

তবে দাম আছে। “এই group-এর সবাই পারবে, শুধু একজন ছাড়া” RBAC-এ সরাসরি লেখা যায় না। তখন group ভাগ করতে হয়। আর “এই namespace-এ কেউ যেন exec করতে না পারে” ধরনের নিয়মের জন্য admission policy বা webhook authorizer লাগে, কারণ সেগুলো না বলতে পারে। অর্থাৎ deny-এর ক্ষমতা সরিয়ে দেওয়া হয়নি, অন্য স্তরে রাখা হয়েছে।

এখান থেকে একটি অভ্যাস তৈরি হয়। নতুন অনুমতি দেওয়ার সময় ভাবুন: এটি ভুল হলে পরে ফেরানো কত সহজ? কারণ পুরোনো Binding কেউ ভুলে গেলে সেটি চুপচাপ ক্ষমতা ধরে রাখে, কোনো deny তাকে ঢাকবে না। তাই নিয়মিত পর্যালোচনা দরকার। kubectl auth can-i --list দিয়ে প্রতিটি ServiceAccount-এর ক্ষমতা মাঝে মাঝে মিলিয়ে দেখুন, আর ব্যবহার হয় না এমন Binding audit log দেখে সরান।

মূল উপলব্ধি: সরল মডেল ভুল কমায়, কিন্তু প্রকাশের ক্ষমতা কমায়। RBAC সরলতা বেছে নিয়েছে, আর ব্যতিক্রমের দায় দিয়েছে admission-কে।

প্র ০২PodSecurityPolicy কেন সরানো হলো, যদি সেটি নিরাপত্তাই দিত?

PSP-র উদ্দেশ্য ঠিক ছিল, সমস্যা ছিল ব্যবহারের নকশায়। কোন Pod-এ কোন PSP খাটবে, তা ঠিক হতো RBAC-এর use verb দিয়ে: Pod যিনি বানাচ্ছেন বা Pod-এর ServiceAccount, যার অনুমতি মিলবে। কিন্তু Deployment-এর Pod বানায় controller, তাই অনুমতি কার, সেটিই গুলিয়ে যেত। অনেক দল ভুল করে সবাইকে সব PSP দিয়ে ফেলত, ফলে সুরক্ষা নামে থাকত, কাজে নয়।

আরও একটি জটিলতা ছিল, PSP Pod বদলেও দিতে পারত। একাধিক PSP মিললে কোনটি বেছে নেওয়া হবে, তার নিজস্ব ক্রম ছিল। চালু করাও ঝুঁকির: admission plugin চালু করলেই, উপযুক্ত PSP না থাকলে নতুন Pod আটকে যেত। আগে থেকে পরীক্ষার সহজ উপায় ছিল না। Kubernetes-এর প্রকল্প ঘোষণাতেও ব্যবহারের এই জটিলতাকেই কারণ হিসেবে বলা হয়েছে। ১.২১-এ deprecated, ১.২৫-এ সরানো।

Pod Security Admission উল্টো পথ নিয়েছে। নিজের নিয়ম লেখার সুবিধা নেই, আছে তিনটি নির্দিষ্ট স্তর। Pod বদলায় না, শুধু হ্যাঁ বা না বলে। নিয়ম ঠিক হয় namespace-এর label দিয়ে, যা দেখা সহজ। আর warn, audit ও dry-run দিয়ে আগে থেকে ফল দেখা যায়, যা আমাদের run-এই দেখেছেন। দাম হলো নমনীয়তা কম, যেমন “এই একটি Pod-কে শুধু একটি ছাড়”, সেই কাজ policy engine-এর।

একটি সরল, সবাই ব্যবহার করে এমন ব্যবস্থা অনেক সময় নিখুঁত কিন্তু অব্যবহৃত ব্যবস্থার চেয়ে বেশি নিরাপত্তা দেয়। ভুল করে খোলা রাখা ক্ষমতাশালী টুলের চেয়ে সঠিকভাবে চালু থাকা সাধারণ টুল ভালো।

মূল উপলব্ধি: নিরাপত্তা-টুলের ব্যবহারযোগ্যতাও একটি নিরাপত্তা-বৈশিষ্ট্য। মানুষ যে নিয়ম বুঝতে পারে না, সেটি চালু রেখেও তাঁরা ভুল করেন।

প্র ০৩Kubernetes-এর কি এমন NetworkPolicy বাতিল করা উচিত নয়, যা কেউ প্রয়োগ করবে না?

আমাদের পরীক্ষায় এটিই সবচেয়ে অস্বস্তিকর ফল ছিল। Policy গৃহীত, তালিকায় দৃশ্যমান, অথচ প্রভাব নেই। নিরাপত্তায় নীরব ব্যর্থতা সবচেয়ে খারাপ ব্যর্থতা, কারণ মানুষ ভাবে সুরক্ষা আছে আর কেউ সেটি আর যাচাই করে না।

তবু এর পেছনে একটি নকশাগত কারণ আছে। Kubernetes API হলো desired state জমা রাখার জায়গা। কে সেটি বাস্তবে আনবে, তা আলাদা। Ingress বস্তুও তাই: controller না থাকলে কিছু হয় না। পর্ব ৪৬-এ এই একই ধাঁচ দেখেছেন। এই ভাগের জন্যই Calico, Cilium বা cloud provider-এর plugin আলাদাভাবে গড়ে উঠতে পেরেছে। চলন্ত plugin-এর পরিচয় API server-এর পক্ষে জানা কঠিন, কারণ CNI কোনো Kubernetes API নয়, প্রতিটি node-এর container runtime তাকে ডাকে। এই cluster-এর kubectl explain networkpolicy-তে spec ছাড়া কোনো status field-ও নেই, তাই বস্তুটি নিজে জানাতে পারে না সে প্রয়োগ হয়েছে কি না।

তাই সমাধান আসে অভ্যাস থেকে। নতুন cluster-এর সেটআপ পরীক্ষায় একটি “নিষিদ্ধ সংযোগ ব্যর্থ হয় কি না” test রাখুন, আর সেটি নিয়মিত চালান। Upgrade বা CNI বদলের পরও চালান। এটি ভুল ধরার একটি নিয়ন্ত্রণ (detective control). Cloud-এ যেমন security group থাকলেও reachability যাচাই করেন, এখানেও তাই। Cluster তৈরির পরপরই তিনটি ছোট Pod চালিয়ে কয়েক মিনিটে এই পরীক্ষাটি করা যায়, আর এর ফল আপনার নিরাপত্তা-নকশার একটি বড় অনুমান যাচাই করে দেয়।

আর Pod-এর সীমায় ভরসা করার সময় মনে রাখবেন, NetworkPolicy IP আর label-এর স্তরে কাজ করে। পরিচয় ধরে যাচাই বা HTTP-স্তরের নিয়ম চাইলে service mesh বা Cilium-এর L7 policy লাগে। সেগুলোর খরচ আর জটিলতা বেশি।

মূল উপলব্ধি: “Object তৈরি হয়েছে” আর “আচরণ বদলেছে” এক কথা নয়। নিরাপত্তার যেকোনো নিয়মের প্রমাণ হলো একটি ব্যর্থ হওয়া চেষ্টা, একটি সফল apply নয়।

প্র ০৪Token-এর মেয়াদ ১ ঘণ্টা চাইলেও cluster ১ বছর দিল। এটি কি ত্রুটি, না বুদ্ধিমান সমঝোতা?

এটি একটি পরিকল্পিত সমঝোতা, আর বড় সিস্টেমে নিরাপত্তা বদলানোর একটি ভালো উদাহরণ। আগে ServiceAccount token ছিল মেয়াদহীন, অনেক client সেটি একবার পড়ে চিরকাল ব্যবহার করত। Bound token চালু হওয়ার পর পুরোনো client library হঠাৎ ১ ঘণ্টা পর ব্যর্থ হতে শুরু করলে হাজারো চলন্ত অ্যাপ ভেঙে যেত।

তাই একটি মধ্যবর্তী পথ নেওয়া হলো। Kubelet ৩৬০৭ সেকেন্ড চাইলে API server আসলে দীর্ঘ মেয়াদ দেয়, আর token-এর ভেতরে warnafter রাখে। ১ ঘণ্টার পর পুরোনো token ব্যবহার হলে API server তা কাজ করতে দেয়, কিন্তু metric ও audit-এ চিহ্ন রাখে। Cluster অপারেটর এভাবে খুঁজে পান, কোন অ্যাপের library পুরোনো। আমাদের run-এ warnafter 3607 আর lifetime_seconds 31536000 ঠিক এই ছবিই দিয়েছে।

নিরাপত্তার দিকে দেখলে এখনো লাভ আছে। Token Pod-এর সাথে বাঁধা, তাই Pod মুছলে তা অচল হয়, যা আমরা প্রায় ১২ সেকেন্ডের মধ্যে দেখেছি। Audience নির্দিষ্ট করা যায়। Kubelet নিয়মিত নতুন token আনে। কিন্তু Pod চলতে থাকা অবস্থায় ফাঁস হওয়া token-এর ঝুঁকি শুধু ১ ঘণ্টার নয়। তাই “মেয়াদ ছোট” বলে নিশ্চিন্ত হওয়া যাবে না। আলাদা audience আর ছোট মেয়াদের token দরকার হলে নিজে projected volume লিখে নিন, আর অনুমতি ছোট রাখুন, কারণ RBAC-ই আসল সীমা। Audit log-এ পুরোনো token-এর চিহ্ন খুঁজে library আগে আপডেট করুন। Managed cluster হলে এই সেটিং নিয়ে provider কী করেন, তা-ও দেখে নিন।

একই ধাঁচ অন্য জায়গাতেও দেখবেন: আগে সতর্ক করা, ব্যবহার মাপা, তারপর ভাঙা। পর্ব ৩৩-এর secret rotation আর PSA-র warn থেকে enforce-এর পথ একই গল্প।

মূল উপলব্ধি: নিরাপত্তা বদলের সবচেয়ে কঠিন অংশ প্রযুক্তি নয়, চলন্ত সিস্টেম না ভেঙে স্থানান্তর। আর নথির কথা মেনে নেওয়ার আগে নিজের cluster-এ মেপে দেখুন।

প্র ০৫নিরাপত্তার সব নিয়ম admission-এ বসালে সমস্যা কোথায়? Webhook নষ্ট হলে কী হয়?

Admission অত্যন্ত আকর্ষণীয় জায়গা, কারণ সব পরিবর্তন এখান দিয়েই যায়। কিন্তু এজন্যই এটি একটি নির্ভরতার কেন্দ্রও। Webhook মানে API server প্রতিটি মিলে যাওয়া অনুরোধে একটি আলাদা সার্ভিসকে ডাকে। সেই সার্ভিস ধীর হলে সব deployment ধীর। নষ্ট হলে দুটি পথ: failurePolicy: Fail রাখলে নতুন বস্তু তৈরি আটকে যায়, এমনকি autoscaling-এর নতুন Pod-ও। Ignore রাখলে নিয়ম চুপচাপ বন্ধ, ঠিক যখন আপনি ভাবছেন সুরক্ষা চালু আছে।

এখানে একটি মুরগি-ডিমের ফাঁদ আছে। Webhook নিজেও Pod হিসেবে চলে। সেই Pod পড়ে গেলে আর নতুন করে উঠতে গেলে যদি একই webhook তাকে যাচাই করতে চায়, তবে সে কখনো উঠবে না। এজন্য webhook-এর নিজের namespace আর kube-system বাদ রাখা, একাধিক replica, ছোট timeout আর আলাদা alert রাখা দরকার। প্রকাশ্য postmortem-এ এ ধরনের ফাঁদের কথা বহুবার উঠে এসেছে, তাই একে তাত্ত্বিক ভাববেন না।

ValidatingAdmissionPolicy এই সমস্যা কমাতেই এসেছে। CEL নিয়ম API server-এর ভেতরেই চলে, তাই আলাদা সার্ভিস নেই, আর network-এর সমস্যাও নেই। সীমা হলো, CEL শুধু সেই বস্তু আর সীমিত প্রসঙ্গ দেখে; registry থেকে স্বাক্ষর আনার মতো বাইরের কাজ করে না। তাই সরল নিয়মে VAP, আর স্বাক্ষর যাচাই বা জটিল নিয়মে Kyverno বা Gatekeeper, এমন মিশ্রণ বাস্তবসম্মত।

আরও একটি সীমা, admission শুধু তৈরি বা বদলের মুহূর্ত দেখে। আগে থেকে চলন্ত Pod সে আটকায় না, আর চলার সময়ের আচরণ দেখে না। তাই audit scan আর Falco-র মতো runtime detection-ও লাগে। Policy-র নিজের চালুর ক্রমও PSA-র মতো: audit বা warn থেকে শুরু, তারপর enforce.

মূল উপলব্ধি: প্রতিটি নিরাপত্তা-নিয়ন্ত্রণ নিজেও একটি উৎপাদন-সিস্টেম। সেটি ব্যর্থ হলে কী হবে, তা চালুর আগেই ঠিক করুন।

অনুশীলন

Cluster ছাড়াও বেশিরভাগ করা যায়। নিজের পরীক্ষার cluster থাকলে একটি আলাদা namespace ব্যবহার করুন, আর শেষে সেটি মুছে দিন। Cloud-এ cluster চালালে খরচ হতে পারে।

  1. কমান্ড: shop namespace-এ catalog নামের ServiceAccount শুধু ConfigMap পড়তে পারবে। Role আর RoleBinding লিখুন, আর একটি হ্যাঁ ও একটি না পরীক্ষার কমান্ড দিন।
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: configmap-reader
      namespace: shop
    rules:
      - apiGroups: [""]
        resources: ["configmaps"]
        verbs: ["get", "list", "watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: catalog-configmap-reader
      namespace: shop
    subjects:
      - kind: ServiceAccount
        name: catalog
        namespace: shop
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: configmap-reader

    যাচাই: kubectl auth can-i get configmaps -n shop --as=system:serviceaccount:shop:catalog থেকে yes, আর একই কমান্ডে get secrets থেকে no আশা করুন। ServiceAccount আগে তৈরি করে নিন। পরিষ্কার করতে Role, RoleBinding আর ServiceAccount নাম ধরে মুছুন।

  2. ফলাফল অনুমান: Namespace-এ Pod a (tier=web), b (tier=api), c (tier=db). Policy শুধু একটি: podSelector: {matchLabels: {tier: db}}, ingress from tier=api. CNI enforcement আছে। a → b, a → c, b → c, c → a-র ফল কী? CNI enforcement না থাকলে?

    a → b ok, কারণ b-কে কোনো policy বাছেনি। a → c বন্ধ, কারণ c বাছা হয়েছে আর a allow তালিকায় নেই। b → c ok. c → a ok, কারণ a বাছা হয়নি, আর policy-র ধরন ingress, তাই c-এর egress বন্ধ নয়। Enforcement না থাকলে চারটিই ok. netpol_matrix.js-এ এই Pod আর policy বসিয়ে মিলিয়ে নিন।

  3. সমালোচনা: AI “সব সমস্যার সমাধান” হিসেবে নিচের সেটিং দিল: একটি ClusterRoleBinding, যা default ServiceAccount-কে cluster-admin দেয়; Pod-এ privileged: true; আর image: myapp:latest. কমপক্ষে চারটি ঝুঁকি বলুন, আর প্রতিটির বিকল্প দিন।
    • default-কে cluster-admin: ওই namespace-এর যে Pod ServiceAccount বলে দেয়নি, সে-ই পুরো cluster-এর মালিক। বিকল্প: আলাদা ServiceAccount আর ছোট Role.
    • ClusterRoleBinding: একটি অ্যাপের জন্য সব namespace খুলে গেল। বিকল্প: RoleBinding.
    • privileged: true: Container-এর সীমা প্রায় উঠে যায়, baseline-ই এটি বাতিল করে। বিকল্প: আসল error খুঁজে নির্দিষ্ট capability বা সঠিক সেটিং।
    • latest: কোন version চলছে বলা যায় না, rollback আর audit কঠিন। বিকল্প: নির্দিষ্ট tag বা digest.
    • বাড়তি: automountServiceAccountToken নিয়ে ভাবা হয়নি, আর এটি কেন দরকার তার কারণও বলা হয়নি। মূল error না বুঝে অনুমতি বাড়ানোই আসল ভুল।
  4. নকশা: একটি চলন্ত cluster-এ তিনটি namespace: prod, staging, monitoring. monitoring-এর একটি agent-এর hostPath লাগে। কোনো namespace-এ Pod Security নেই। কোন ক্রমে কোন label বসাবেন, যাতে কিছু না ভাঙে?
    1. সব namespace-এ kubectl label --dry-run=server দিয়ে দেখুন, কোন Pod কোন স্তর ভাঙছে।
    2. staging আর prod-এ warn=restricted ও audit=restricted বসান, enforce এখনই নয়।
    3. staging-এ manifest ঠিক করুন, লঙ্ঘন শূন্য হলে enforce=baseline, পরে enforce=restricted, সাথে enforce-version.
    4. একই পথে prod, আগে baseline. কিছু অ্যাপ restricted না পারলে baseline-এ রেখে কারণ লিখে রাখুন।
    5. monitoring-এ agent-টি আলাদা namespace-এ সরান। সেখানে privileged স্তর থাকবে, কিন্তু RBAC কড়া আর মালিক নির্দিষ্ট। বাকি namespace baseline.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমKubernetes API server-এ একটি Pod তৈরির অনুরোধের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Authentication — পরিচয় যাচাই
  2. Authorization — RBAC-এ অনুমতি যাচাই
  3. Mutating admission — spec-এ যোগ বা বদল
  4. Validating admission — Pod Security ও policy যাচাই
  5. etcd-তে জমা

kubectl get secrets চালিয়ে পেলেন: Error from server (Forbidden): secrets is forbidden: User "dev" cannot list resource "secrets". এর অর্থ কী?

  • Token-এর মেয়াদ শেষ, আবার login করতে হবে
  • পরিচয় ঠিক আছে, কিন্তু অনুমতি নেই
  • Pod Security নিয়ম অনুরোধটি বাতিল করেছে
  • Namespace-টি খুঁজে পাওয়া যায়নি

একটি ClusterRole view-কে namespace shop-এর একটি RoleBinding দিয়ে ServiceAccount auditor-কে দেওয়া হলো। auditor কোথায় Pod পড়তে পারবে?

  • সব namespace-এ, কারণ এটি ClusterRole
  • কোথাও নয়, RoleBinding ClusterRole ব্যবহার করতে পারে না
  • শুধু shop namespace-এ
  • শুধু default namespace-এ

একটি namespace-এ default-deny NetworkPolicy দেওয়া হলো। kubectl get netpol-এ সেটি দেখাচ্ছে, কিন্তু নিষিদ্ধ Pod তখনও সংযোগ করতে পারছে। সবচেয়ে সম্ভাব্য কারণ কী?

  • Kubelet আবার চালু করা হয়নি
  • RBAC-এ Pod-গুলোর network অনুমতি নেই
  • Namespace-এ Pod Security label নেই
  • Cluster-এর CNI plugin NetworkPolicy প্রয়োগ করে না

Namespace-এ pod-security.kubernetes.io/enforce=restricted label বসানো হলো। আগে থেকে চলন্ত, নিয়ম-ভাঙা Pod-গুলোর কী হবে?

  • চলতে থাকবে, তবে নতুন বা পুনরায় তৈরি Pod নিয়ম না মানলে বাতিল হবে
  • সাথে সাথে মুছে যাবে
  • PSA নিজে Pod-এর spec ঠিক করে দেবে
  • Label বসানোই ব্যর্থ হবে

Pod-এ runAsNonRoot: true আছে, runAsUser নেই, আর image-এর ডিফল্ট user root. বাকি সব restricted শর্ত মানা হয়েছে। Restricted namespace-এ কী হবে?

  • Admission অনুরোধটি বাতিল করবে
  • Admission পেরোবে, কিন্তু kubelet container চালু করবে না
  • Kubernetes user বদলে 1000 করে দেবে
  • Root হিসেবে স্বাভাবিকভাবে চলবে

Kubernetes ১.২৫ বা তার পরের cluster-এ kind: PodSecurityPolicy apply করলে কী হওয়ার কথা?

  • Policy চালু হয়ে Pod-গুলো সুরক্ষিত হবে
  • PSA নিজে থেকে সেটিকে label-এ বদলে নেবে
  • Resource type না চেনার error আসবে
  • API server বন্ধ হয়ে যাবে

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রতিটি অনুরোধের পথ: authentication → authorization → admission → etcd. ৪০১ পরিচয়ের, ৪০৩ অনুমতির।
  2. RBAC additive: Role অনুমতি বলে, Binding কাকে আর কোথায় বলে। Wildcard আর cluster-admin এড়িয়ে can-i --as দিয়ে যাচাই করুন।
  3. Token পরিচয় দেয়, অনুমতি নয়। Projected token Pod-এ বাঁধা, তবে মেয়াদ নিজের cluster-এ মেপে দেখুন।
  4. NetworkPolicy প্রয়োগ করে CNI plugin. একটি নিষিদ্ধ সংযোগের ব্যর্থতাই একমাত্র প্রমাণ।
  5. PSA namespace-এর label দেখে নতুন Pod যাচাই করে; securityContext-এ non-root, capability বাদ, seccomp আর read-only root দিন; সরাসরি enforce না করে আগে warn আর audit.

পরের পর্বে: এতক্ষণ YAML হাতে লিখেছেন। পর্ব ৫০-এ দেখবেন Helm, Kustomize আর operator দিয়ে একই manifest কীভাবে প্যাকেজ করা হয়, পরিবেশভেদে সাজানো হয়, আর CRD দিয়ে Kubernetes-কে বাড়ানো হয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান