تقوية أمن السحابة وKubernetes

تصليب Kubernetes: أمان الحاويات (Pod Security)

18 دقيقة الدرس 5 من 28

تصليب Kubernetes: أمان الحاويات (Pod Security)

كل حمل عمل يعمل في Kubernetes يُنفَّذ داخل Pod. افتراضياً، يرث ذلك الـPod نواة المضيف، ويمكنه العمل كـroot، وتركيب مسارات المضيف بشكل تعسفي — حاوية واحدة مخترقة يمكنها التحول إلى سيطرة كاملة على الكلستر. أمان الـPod هو ممارسة تجريد هذه الافتراضات بحيث يهبط هروب الحاوية في صندوق رمل محدود بدلاً من shell بصلاحيات root مع وصول إلى كل سر في الكلستر.

يتناول هذا الدرس النموذج ثلاثي الطبقات الذي تعتمده فرق الإنتاج في الشركات الكبرى: معايير أمان الـPod (PSS) على مستوى الـnamespace، وsecurityContext على مستوى الـPod والحاوية، والإعدادات الافتراضية الآمنة التي تُطبّق السلوك الآمن حتى حين ينسى المطورون.

معايير أمان الـPod: السياسة على مستوى الـNamespace

حلّت معايير أمان الـPod (PSS) محل PodSecurityPolicy المُهمَل في Kubernetes 1.25. تُعرّف PSS ثلاثة مستويات سياسة مُسمّاة يُطبّقها متحكم قبول مُدمج — بدون CRDs، ولا webhooks، ولا تبعيات خارجية.

  • Privileged: بدون قيود تماماً. محجوز لـnamespaces النظام مثل kube-system وإضافات CNI التي تحتاج فعلاً إلى وصول المضيف.
  • Baseline: يمنع أخطر الانتهاكات (الحاويات المميزة، hostPID، hostIPC، صلاحيات خطيرة مثل NET_ADMIN) بينما يسمح لمعظم أحمال العمل القديمة بالعمل دون تعديل.
  • Restricted: مقيّد بشدة — يتطلب UID غير root، ويُسقط كل الصلاحيات، ويمنع تصعيد الامتيازات، ويُطبّق ملف seccomp. المعيار الذهبي لأحمال عمل التطبيقات.

يمكن ضبط كل مستوى في ثلاثة أوضاع: enforce (رفض الـPods المنتهِكة عند القبول)، وaudit (السماح لكنّ إطلاق حدث تدقيق للانتهاك)، وwarn (السماح لكنّ إظهار تحذير لعميل API). النمط المُعتمد في بيئة الإنتاج هو تطبيق warn وaudit بوضع Restricted في كل مكان أولاً، ومراقبة الانتهاكات لفترة، وإصلاح أحمال العمل، ثم تحويل الـnamespaces إلى enforce.

# تطبيق الأوضاع الثلاثة على namespace الإنتاج kubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/enforce-version=latest \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/warn-version=latest \ pod-security.kubernetes.io/audit=restricted \ pod-security.kubernetes.io/audit-version=latest # اختبار جاف: ما الذي سيرفضه Restricted في هذا الـnamespace اليوم؟ kubectl label --dry-run=server --overwrite namespace production \ pod-security.kubernetes.io/enforce=restricted 2>&1 | grep Warning
حدّد الإصدار، لا تكتفِ بـlatest. استخدام enforce-version=latest يعني أن ترقية Kubernetes يمكنها رفض Pods كانت متوافقة سابقاً بمجرد إضافة فحوصات أكثر صرامة لملف Restricted. في الإنتاج، حدّد إصداراً محدداً مثل v1.30 وقم بالترقية بتخطيط خلال نوافذ الصيانة.

securityContext: تصليب الـPods والحاويات بشكل فردي

تضبط PSS سقف السياسة. securityContext هو المكان الذي تُطبّق فيه السياسة لكل حمل عمل. يُتيح Kubernetes مستويين: spec.securityContext (على مستوى الـPod، يُطبّق على كل الحاويات) وspec.containers[].securityContext (على مستوى الحاوية، يُلغي مستوى الـPod لتلك الحاوية).

فيما يلي deployment جاهز للإنتاج يستوفي معيار Restricted مع نية موثّقة صريحة لكل حقل:

apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: production spec: replicas: 3 selector: matchLabels: app: api-server template: metadata: labels: app: api-server spec: # سياق الأمان على مستوى الـPod securityContext: runAsNonRoot: true # رفض UID root وقت التشغيل، ليس مجرد اقتراح runAsUser: 1000 # UID صريح؛ تجنب UID 0 والـUIDs المعروفة runAsGroup: 3000 fsGroup: 2000 # المجلدات مملوكة لهذا الـGID seccompProfile: type: RuntimeDefault # فلتر استدعاءات kernel عبر runtime الحاوية supplementalGroups: [] # لا عضويات مجموعات إضافية containers: - name: api image: registry.example.com/api:v2.4.1@sha256:abc123 # digest مُثبَّت securityContext: allowPrivilegeEscalation: false # يمنع setuid/setgid وsudo readOnlyRootFilesystem: true # نظام ملفات الحاوية غير قابل للتعديل capabilities: drop: ["ALL"] # ابدأ بصفر صلاحيات Linux # add: ["NET_BIND_SERVICE"] # أضف فقط إن احتجت منفذاً < 1024 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi" volumeMounts: - name: tmp mountPath: /tmp # مساحة كتابة مؤقتة، ليس نظام الملفات الجذري - name: cache mountPath: /var/cache/app volumes: - name: tmp emptyDir: {} - name: cache emptyDir: {} automountServiceAccountToken: false # لا وصول لـAPI server ما لم يكن ضرورياً
Pod Security layers: PSS namespace label, Pod securityContext, Container securityContext Namespace: production PSS Label: enforce=restricted Pod spec.securityContext (Pod-level) runAsNonRoot · runAsUser · fsGroup · seccompProfile Container: api allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities.drop: [ALL] seccompProfile: RuntimeDefault inherits Pod-level + container overrides Container: log-agent (sidecar) allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities.drop: [ALL] runAsUser: 2000 (overrides Pod) container-level overrides Pod-level UID enforces
طبقات أمان الـPod: تسمية PSS في الـnamespace تُطبّق السياسة عند القبول؛ securityContext على مستوى الـPod يضع الافتراضات المشتركة؛ وإعدادات مستوى الحاوية تُلغيها لكل حاوية.

الإعدادات الافتراضية الآمنة في وقت التشغيل

الاعتماد فقط على تذكّر المطورين لضبط securityContext لا يتوسع. تستخدم منصات الإنتاج آليتين للإنفاذ لجعل المسار الآمن هو المسار الافتراضي.

1. Admission Webhooks مع OPA/Kyverno

يمكن لـwebhook قبول مُحوِّل (mutating) حقن securityContext معقول في كل Pod لا يحدده. يمكن لـwebhook تحقق (validating) بعدها رفض الـPods التي لا تزال تنتهك السياسة بعد التحويل. Kyverno هو الخيار الأبسط للفرق الأصيلة في Kubernetes؛ OPA/Gatekeeper يُتيح مرونة أكبر للسياسات المعقدة المشتركة عبر السحابات.

# سياسة Kyverno: تحويل أي Pod يفتقد seccompProfile apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: add-default-seccomp spec: rules: - name: add-seccomp-profile match: any: - resources: kinds: ["Pod"] mutate: patchStrategicMerge: spec: securityContext: +(seccompProfile): # أضف فقط إن لم يكن مضبوطاً type: RuntimeDefault containers: - (name): "?*" securityContext: +(allowPrivilegeEscalation): false +(readOnlyRootFilesystem): true capabilities: +(drop): ["ALL"]

2. seccomp RuntimeDefault كإعداد افتراضي عالمي

منذ Kubernetes 1.27، يمكنك تفعيل --feature-gates=SeccompDefault=true على الـkubelet وضبط --seccomp-default لتطبيق ملف RuntimeDefault على جميع الـPods التي لا تحدده. هذا هو أقرب ما يصل إليه Kubernetes من إعداد افتراضي آمن عالمي على مستوى العقدة.

فضّل دائماً RuntimeDefault على Unconfined. يحجب ملف seccomp الخاص بـRuntimeDefault (المُقدَّم من containerd أو cri-o) حوالي 300 syscall خطير تشمل ptrace وmount وunshare — وهي الأدوات الأساسية في سلاسل استغلال الهروب من الحاويات. تكلفة الأداء غير قابلة للقياس في كل أحمال العمل تقريباً. لا يوجد سبب وجيه لتشغيل Unconfined في الإنتاج إلا لأدوات الأمان المخصصة.

أنماط الإخفاق في الإنتاج وتكلفتها

فهم سبب وجود كل حقل يتطلب رؤية ما يحدث بدونه:

  • غياب runAsNonRoot: true: صورة مبنية بدون تعليمة USER تعمل كـUID 0 داخل الحاوية. إذا كانت هناك ثغرة في kernel، يتطابق UID 0 داخلياً مع root خارجياً. CVE-2019-5736 (كتابة runc) تطلّبت root للاستغلال.
  • غياب allowPrivilegeEscalation: false: الثنائيات ذات بت setuid (مثل sudo وnewgrp وpkexec) يمكنها التصعيد إلى root حتى حين تبدأ الحاوية كمستخدم غير root. هكذا عملت CVE-2021-4034 (PwnKit).
  • غياب readOnlyRootFilesystem: true: المهاجم الذي يحقق تنفيذ كود يمكنه كتابة أدوات استمرار، أو سرقة البيانات إلى القرص، أو زرع reverse shell. نظام ملفات للقراءة فقط يحصر نطاق الضرر في العمليات الذاكرية.
  • غياب إسقاط الصلاحيات: المجموعة الافتراضية من صلاحيات Linux الممنوحة للحاوية تتضمن NET_RAW (صياغة حزم خام، ARP spoofing) وSYS_CHROOT وMKNOD. أسقط الكل وأضف فقط ما ثبتت حاجته.
  • غياب automountServiceAccountToken: false: كل Pod يحصل افتراضياً على token لحساب الخدمة. في حاوية مخترقة، يوفر هذا الـtoken وصولاً لـAPI server وهو المحور الرئيسي لهجمات التحرك الجانبي داخل الكلستر.
صلاحيات root على مستوى الصورة هي الاكتشاف الأكثر شيوعاً في عمليات تدقيق الإنتاج. تضبط الفرق runAsUser: 1000 في manifest التوزيع لكنها تنسى إضافة تعليمة USER 1000 إلى Dockerfile. سيُشغّل Kubernetes الحاوية بالـUID الذي تحدده — لكن إذا بُني البرنامج الثنائي في الصورة متوقعاً UID 0، قد يتعطل بسبب أذونات الملفات. ابنِ دائماً مع USER nonroot في Dockerfile وأطبقه على مستوى الـPod أيضاً.

التحقق من التصليب

بعد تطبيق إعدادات securityContext، تحقق منها دون تخمين:

# تأكيد UID الفعلي للحاوية الشغّالة kubectl exec -n production deploy/api-server -- id # المتوقع: uid=1000 gid=3000 groups=2000 # تأكيد منع تصعيد الامتيازات kubectl exec -n production deploy/api-server -- sudo id # المتوقع: sudo: command not found (أو permission denied) # تأكيد أن نظام الملفات الجذري للقراءة فقط kubectl exec -n production deploy/api-server -- touch /test-write # المتوقع: touch: cannot touch '/test-write': Read-only file system # تأكيد عدم وجود صلاحيات إضافية kubectl exec -n production deploy/api-server -- cat /proc/1/status | grep Cap # CapPrm وCapEff يجب أن تكون 0000000000000000 # فحص ملف seccomp النشط على الحاوية الشغّالة kubectl get pod -n production -l app=api-server -o jsonpath=\ '{.items[0].spec.securityContext.seccompProfile}'

ادمج هذه الفحوصات مع ماسح سياسات مثل Trivy (trivy k8s --report summary cluster) أو kube-bench للتوافق مع معيار CIS. تشغيل هذه الفحوصات في pipeline CI الخاص بك — على الـmanifests المُصيَّرة قبل قبول الكلستر — يكتشف الانحدارات قبل وصولها إلى الإنتاج.