قدم Kubernetes v1.25 دعم ألفا لنظام التشغيل Linux مساحات أسماء المستخدمين (المستخدمين). يتم وصف هذه الميزة كطبقة عزل إضافية تعمل على تحسين أمان المضيف وتمنع العديد من سيناريوهات الهروب المعروفة للحاويات.
في هذه المدونة، سنتعمق في بعض الاستخدامات المحتملة والتعقيدات الخاصة بمساحات أسماء المستخدمين من أجل توفير مجموعة من أفضل الممارسات لمشغلي المجموعات الذين يهدفون إلى تعزيز أمان مجموعاتهم.
خلفية
مساحات أسماء المستخدمين ليست مفهومًا جديدًا. يذكر دليل Linux kernel المستخدمين الذين يبدأون بالإصدار 3.8. منذ ذلك الحين، أصبح المستخدمون التكنولوجيا الأساسية وراءها حاويات بلا جذور. ونظرًا لأن إصدارات النواة الحالية تقع في نطاق 5.x، فقد أتيحت للمستخدمين فرصة كبيرة للتطور. ومع ذلك، يجب الاعتراف بتعقيداتها وآثارها الأمنية المحتملة: فاستخدامها مفتوح مسارات التعليمات البرمجية التي لم يتم اختبارها مسبقًا. على الرغم من توفرها بسهولة في النواة، اختارت العديد من توزيعات Linux تعطيل المستخدمين افتراضيًا للسماح لهذه الميزة بالنضج. اعتمد مطورو Kubernetes نهجًا مشابهًا.
من المهم ملاحظة أنه في Kubernetes، لا تزال مساحات أسماء المستخدمين في مرحلة ألفا. إنهم ليسوا جاهزين للإنتاج. في الواقع، المكان الوحيد الذي يمكن اختبار المستخدمين فيه في المجموعات المُدارة هو مجموعات ألفا من Google Kubernetes Engine (GKE). جميع الاختبارات الموضحة في هذا المنشور حدثت هناك.
كيف تعمل مساحات أسماء المستخدمين على تحسين أمان حمل العمل
تسهل مساحات أسماء المستخدمين على مشغلي المجموعة تخفيف التأثير المحتمل للهروب من الحاوية والسماح بمنح امتيازات إضافية بشكل أكثر أمانًا لوحدات معينة.
التخفيف من تأثير هروب الحاويات
الدافع الرئيسي وراء مساحات أسماء المستخدمين في سياق الحاوية هو الحد من التأثير المحتمل للهروب من الحاوية. عند تشغيل عملية مرتبطة بالحاوية كجذر هروب إلى المضيف، فإنها لا تزال تعتبر عملية مميزة بمعرف المستخدم (UID) الخاص بها يساوي 0. ومع ذلك، تقدم مساحات أسماء المستخدمين تعيينًا متسقًا بين معرفات المستخدم على مستوى المضيف ومعرفات المستخدم على مستوى الحاوية التي تضمن أن UID 0 على الحاوية يتوافق مع UID غير صفر على المضيف. من أجل القضاء على احتمال تداخل UID مع المضيف، تتلقى كل حجرة 64 ألف معرف مستخدم للاستخدام الخاص.
ونتيجة لذلك، حتى لو كانت العملية جذرًا (أي UID 0) داخل الحاوية، فإن الهروب إلى المضيف لن يؤدي إلا إلى تخويلها الوصول إلى الموارد المرتبطة بـ UID X. على سبيل المثال، في الشكل أعلاه، لن يتمكن UID 0 على Pod A إلا من الوصول إلى الموارد التي يحق لـ UID 65536 الوصول إليها على مستوى المضيف.
علاوة على ذلك، سيتم حظر العملية التي تم الهروب منها بمساحة اسم المستخدم من الوصول إلى الموارد مثل:
-
/etcملفات التكوين -
الأجهزة في
/dev -
ال
/home/rootدليل لتخزين الأسرار المحتملة -
تكوين Kubelet، والذي يُستخدم بشكل شائع للحركة الجانبية داخل المجموعة ولا يمكن قراءته إلا من خلال عملية الجذر على مضيف المجموعة
gke-test-default-pool-c15ade0e-yz47 / # ls -la /var/lib/kubelet/kubeconfig
-rw-r--r-- 1 root root 554 Dec 10 16:11 /var/lib/kubelet/kubeconfig`
تعيين معرف المستخدم يمنع استغلال العديد من نقاط الضعف البارزة. استخدام أزورسكيب على سبيل المثال، يمكن للممثل الهروب إلى المضيف واستدعاء واجهة برمجة التطبيقات (API) باستخدام ملف تكوين kubelet. ومع ذلك، مع مساحة اسم المستخدم، لن يتمكنوا من قراءة الملف وسيتم كسر سلسلة الاستغلال، مما يؤدي إلى تجنب الاستيلاء على الحاويات عبر الحسابات.
تتمثل الفائدة الثانوية لتعيين UID في الفصل الأفضل بين القرون الموجودة على نفس العقدة العاملة في حالة حدوث هروب للحاوية. يفترض هذا أن البودات الفردية لديها مخططات مختلفة لرسم UID/GID على المضيف، والتي يجب التعامل معها دائمًا بواسطة kubelet على مجموعة مُدارة.
الميزة الثالثة التي يتم التغاضي عنها غالبًا والتي يقدمها تعيين UID هي فصل حدود الموارد. في نظام التشغيل Linux، تكون مجموعات التحكم ومساحات الأسماء مسؤولة عن “تحزيم” أحمال العمل المختلفة على نفس المضيف، حيث يتلقى كل منها وحدة المعالجة المركزية الخاصة به وحصة الذاكرة وما إلى ذلك. ومع ذلك، هناك بعض القيود مثل عدد إشعارات نظام الملفات تظل مرتبطة بـ UID/GID. نظرًا لأن العديد من البودات تستخدم UID 1000، فإنها تتقاسم هذا الحد؛ تعمل إعادة تعيين UID المضيف التلقائي لميزة المستخدمين على حل هذه المشكلة.
تجريد الامتيازات غير الضرورية
يتم عزل الحاويات المميزة التي تعمل في مساحة اسم المستخدم الخاصة بها، وبالتالي لا يمكن أن تؤدي قدراتها إلى الإضرار بالمضيف. على سبيل المثال، يفتقر Kubernetes إلى دعم تركيب نظام ملفات FUSE. أحد الحلول هو إضافة مواصفات لتركيب مجموعة GCP عند postStart الحدث مع مطلوب CAP_SYS_ADMIN أذونات مساحة الاسم الأولية.
spec:
...
containers:
- name: my-container
securityContext:
privileged: true
capabilities:
add:
- SYS_ADMIN
lifecycle:
postStart:
exec:
command: ["gcsfuse", "-o", "nonempty", "your-bucket-name", "/etc/letsencrypt"]
لكن، SYS_ADMIN هي قدرة قوية. في هذه الحالة، سيؤدي تنفيذ مساحة اسم المستخدم إلى إلغاء الحاجة إلى التحديد privileged:true ومنحة SYS_ADMIN، مما يقلل بشكل كبير من سطح هجوم المضيف.
السيناريو الشائع الآخر هو توصيل الكبسولة بشبكة VPN. تتطلب الخدمات الشائعة مثل OpenVPN NET_ADMIN القدرة على تكوين إعدادات الشبكة الخاصة بالجراب. عند عزلها بواسطة مساحة اسم مستخدم، لا تتمكن الحجرة من اختطاف تكوين شبكة المضيف في حالة هروب الحاوية.
القيود
كما هو متوقع من ميزات ألفا، تخضع مساحات أسماء المستخدمين في Kubernetes لبعض القيود.
لا يزال من الممكن أن يكون الجذر غير جذري
في Linux، يتم حجز بعض الإجراءات للمستخدمين المميزين. حتى إذا تم تنفيذ الإجراءات داخل مساحة اسم المستخدم، فإن مسارات التعليمات البرمجية والوظائف المضافة توفر موجهات هجوم جديدة للجهات الفاعلة التي تشكل تهديدًا مع القدرة على تنفيذ الأوامر في حاوية مخترقة.
يعتبر CVE-2022-0185. يتطلب استغلال هذه الثغرة الأمنية وجود مترجم CAP_SYS_ADMIN يتم منحها تلقائيًا من قبل المستخدمين، مما يعكس كيف يمكن لميزة الأمان أن تولد بالفعل حالة من عدم الأمان على مستوى النواة. ومع ذلك، فإن إنشاء مساحة اسم المستخدم ليس عملية مميزة. على سبيل المثال، على الرغم من حظر إنشاء مساحة اسم PID، إلا أنه يُسمح بإنشاء مساحة اسم مستخدم جديدة في حاوية GKE التالية:
shay_b@cloudshell:~ (shay-junk-cluster)$ kubectl exec -it test-userns -- sh
/ # unshare -p
unshare: unshare(0x20000000): Operation not permitted
/ # readlink /proc/$$/ns/user
user:[4026531837]
/ # unshare -U
test-userns:~$ readlink /proc/$$/ns/user
user:[4026532869]
نظرا إلى unshare(CLONE_NEWUSER) يقوم syscall بإنشاء مساحات أسماء مستخدمين جديدة – مما يؤدي إلى استخدام محلي، وإن كان ذلك محفوفًا بالمخاطر SYS_ADMIN القدرات – من الضروري حظر اتصال النظام باستخدام ملف تعريف seccomp. لحسن الحظ، يقوم Kubernetes v1.25 بترقية ملف تعريف seccomp الافتراضي الميزة إلى الإصدار التجريبي، مما يقلل من سطح الهجوم الأولي لعمليات النشر.
فئات عبء العمل
هناك مجموعة متنوعة من العوامل التي تؤثر على ما إذا كانت مساحات أسماء المستخدمين مناسبة للكبسولة أم لا. تم تصميم شجرة القرار التالية لمشغلي المجموعة لتقييم مدى ملاءمة أعباء العمل الخاصة بهم في ظل تطبيق الميزة الحالية:
إن أبرز ثلاث فئات لأحمال العمل غير المتوافقة مع مساحات أسماء المستخدمين هي تلك التي تحتوي على وحدات تخزين مشتركة، وتلك التي تتطلب الوصول الأولي إلى مساحة الاسم، وتلك التي تتطلب الوصول إلى مساحة الاسم المشتركة مع المضيف (راجع الملحق ب).
تمثل هذه فقط القيود الأكثر شيوعًا، وهناك العديد من الحالات المحتملة الأخرى. قامت Wiz Research بالتحقيق في مئات البيئات السحابية لتحديد بعض هذه القيود ووجدت أنه في البيئات المعبأة في حاويات:
-
تعمل أكثر من 30% من البودات بمساحات أسماء مشتركة مع المضيف
-
44% من القرون بها أحجام مشكلة مثبتة في مواصفاتها
-
24.4% من القرون مميزة
-
1.8% من القرون بها
allowPrivilegeEscalation: true
تسلط هذه الأرقام الضوء على مدى عدم ملاءمة مساحات أسماء المستخدمين لجزء كبير من أحمال العمل في بيئات الإنتاج. يجب أن تقوم الأبحاث المستقبلية بمزيد من التحقيق في أنواع الامتيازات الممنوحة للقرون وتأثير المستخدمين على تقليل امتيازات القرون.
ملخص
توفر مساحات أسماء المستخدمين في Kubernetes عدة طرق لتحسين أمان عبء العمل على الرغم من أنها يمكنها في ظل تكوينات معينة غير شائعة زيادة سطح الهجوم للمجموعات. بدءًا من إعادة تعيين UIDs وحتى تقليل الامتيازات، تعمل مساحات أسماء المستخدمين على تحسين عزل أحمال العمل القابلة للتطبيق. من أجل مساعدة الممارسين على قياس مدى ملاءمة عبء العمل، قمنا بتجميع شجرة قرارات تصف أعباء العمل المتوافقة مع الميزة. نظرًا لتعقيد المستخدمين، سيكون من المثير للاهتمام مراقبة عملية نضج الميزة ومعرفة كيفية معالجة المطورين للقيود الحالية.
الملاحق
الملحق أ – المستخدمون في Docker
يعتمد دعم مساحات أسماء المستخدمين على مستوى Kubernetes على دعم وقت تشغيل الحاوية. كان Docker واحدًا من أوائل المستخدمين الذين تبنوا المستخدمين. خذ بعين الاعتبار تعيين المستخدم التالي عند تشغيل البرنامج الخفي Docker معه userns-remap ممكّن بعد التحرير /etc/docker/daemon.json.
kali@kali:/tmp$ sudo docker run -it --rm alpine sleep 1h
---
kali@kali:/usr/local/lib/systemd/system$ ps aux | grep sleep
root 2919 0.0 0.0 10752 5128 pts/1 S+ 10:19 0:00 sudo docker run -it --rm alpine sleep 1h
root 2920 0.8 0.8 1423596 53860 pts/1 Sl+ 10:19 0:00 docker run -it --rm alpine sleep 1h
296608 2969 0.4 0.0 1608 4 pts/0 Ss+ 10:19 0:00 sleep 1h
على الرغم من sudo وعمليات Docker تعمل كجذر، وعملية Bash لها UID 296608. وذلك لأنه تم تكوين مستخدم Docker الافتراضي لإعادة تعيين معرفات المستخدم في /etc/subuid مثل dockremap:296608:65536. يتوافق المستخدم الجذر في الحاوية مع UID 296608 على المضيف، وUID 1 إلى UID 296609، وهكذا.
بالإضافة إلى ذلك، يختلف مستوى عزل مساحة الاسم بين الحاوية والمضيف، حيث تكون مساحة الاسم المشتركة الوحيدة هي مساحة الاسم الزمنية:
kali@kali:/etc/docker$ sudo ls -la /proc/1/ns
lrwxrwxrwx 1 root root 0 Dec 16 14:04 cgroup -> 'cgroup:[4026531835]'
…
lrwxrwxrwx 1 root root 0 Dec 16 14:04 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Dec 16 14:04 uts -> 'uts:[4026531838]'
kali@kali:/etc/docker$ sudo docker run -it --rm alpine sh
/ # ls -la /proc/$$/ns
lrwxrwxrwx 1 root root 0 Dec 16 12:05 cgroup -> cgroup:[4026532866]
…
lrwxrwxrwx 1 root root 0 Dec 16 12:05 user -> user:[4026532672]
lrwxrwxrwx 1 root root 0 Dec 16 12:05 uts -> uts:[4026532674]
الملحق ب – شجرة القرار
البودات التي تتطلب امتيازات تحكمها مساحات أسماء المستخدمين الأولية
يضمن الهيكل الهرمي لمساحات الأسماء خضوع الموارد التي تديرها جميع مساحات الأسماء غير المستخدمة لمالك مساحة اسم المستخدم. ومع ذلك، ليست كافة الموارد مرتبطة بمساحات الأسماء. كما هو موضح في دليل مستخدم kernel:
هناك العديد من العمليات المميزة التي تؤثر على الموارد غير المرتبطة بأي نوع من مساحة الاسم، على سبيل المثال، تغيير وقت النظام (أي التقويم) (الذي يحكمه CAP_SYS_TIME)، وتحميل وحدة kernel (يحكمها CAP_SYS_MODULE)، وإنشاء جهاز (يحكمه CAP_MKNOD). يمكن فقط للعملية التي تتمتع بالامتيازات في مساحة اسم المستخدم الأولية إجراء مثل هذه العمليات.
ماذا يحدث إذا حاولت الركض mknod في مساحة اسم المستخدم بعد تمكين إعادة تعيين المستخدم في البرنامج الخفي Docker (انظر الملحق أ)؟
kali@kali:/tmp$ sudo docker run -it --rm alpine sh
/ # mknod /tmp/null c 1 3
mknod: /tmp/null: Operation not permitted
وهذا يعني أن أحمال العمل التي تتطلب الوصول إلى الموارد و/أو مكالمات النظام المرتبطة بمساحات الأسماء الأولية لا يمكنها استخدام هذه الميزة. وعلاوة على ذلك، سيكون من المستحيل إضافة هذه القدرات في securityContext من جراب.
يحدد KEP واحدًا بشكل صحيح قصة المستخدم وصف سبب عدم رغبة مسؤول المجموعة في استخدام الميزة: “بصفتي مسؤول المجموعة، أريد السماح بتشغيل بعض القرون في مساحة اسم المستخدم المضيف إذا كانوا بحاجة إلى ميزة متاحة فقط في مساحة اسم المستخدم هذه، مثل تحميل وحدة kernel مع CAP_SYS_MODULE“.
الكبسولات التي تتطلب قدرات على مساحات أسماء المضيفين
النظر في الحاوية التي تتطلب CAP_SYS_ADMIN على مكدس الشبكة المضيفة للاستفادة من الشبكة المضيفة أو تتطلب حاوية التصحيح CAP_SYS_PTRACE على مساحة اسم PID للمضيف. سيتعين على هذه القرون الاستمرار في استخدام واجهات المشاركة الحالية. تسمح مواصفات pod حاليًا بتكوين ثلاثة أنواع من مساحات الأسماء القابلة للمشاركة:
يتعارض وجود أي مما سبق في مواصفات البود مع الاستخدام المحتمل لمساحات أسماء المستخدمين.
أعباء العمل الرسمية
في الوقت الحالي، يُسمح فقط للأقراص التي تحتوي على مجموعة أنواع وحدات التخزين التالية باستخدام مساحات أسماء المستخدمين:
-
configmap
-
سر
-
downwardAPI
-
فارغةDir
-
المتوقع
وذلك لأنه لا يمكن مشاركة الموارد بين البودات بمجرد أن تكون محكومة بمساحة اسم مستخدم مختلفة. يصف KEP خطة لمعالجة هذا القيد في المرحلة 2 عن طريق تعيين القرون باستخدام وحدة التخزين المشتركة لنفس نظام UID/GUID. سيؤدي هذا إلى إضعاف الوضع الأمني للعقدة ويمثل مقايضة الأمان/سهولة الاستخدام.
قيود أخرى
إضافة hostUser: true غير متوافق مع الإعداد hostNetwork, hostIPC، أو hostPID وعلى هذا النحو يؤدي إلى فقدان تفاصيل الامتياز. وهذا يفرض اتباع نهج ثنائي إلى حد ما للترحيل: إما عدم استخدام المستخدمين على الإطلاق أو استخدام المستخدمين فقط. يؤثر فقدان التفاصيل هذا أيضًا على القدرات، مما يؤدي إلى إضافة قدرات إليها securityContext مستحيل إذا تم استخدامه مع مساحات أسماء المستخدمين.
تؤثر مساحات أسماء المستخدمين على تعيين ملكية الملفات. وفقًا للوثائق: “سيقوم kubelet بتعيين معرفات UID/GIDs أعلى من تلك الموجودة في القرون. لذلك، لضمان أكبر قدر ممكن من العزل، يجب أن تكون معرفات UID/GID التي تستخدمها ملفات المضيف وعملياته في النطاق 0-65535.”
يعثر الأمر التالي على أي ملفات موجودة على العقدة مملوكة للمستخدم > 65535 وبالتالي يجب أن يتم تخزينها:
ls -anR / 2>/dev/null | awk '$3 > 65535 print $3 " " $4 " " $9'
