اگر روی لینوکس با SELinux در حالت enforcing، Kubernetes اجرا می‌کنید، از هم‌اکنون برنامه‌ریزی کنید: در نسخه آینده (احتمالاً v1.37) قرار است feature gate به نام SELinuxMount به‌صورت پیش‌فرض فعال شود. این تغییر باعث می‌شود راه‌اندازی volume برای اغلب ورک‌لودها سریع‌تر شود، ولی می‌تواند اپلیکیشن‌هایی که هنوز به مدل قدیمی recursive relabeling وابسته‌اند را به‌طور نامحسوس دچار مشکل کند (مثلاً وقتی یک volume بین Pods دارای سطوح دسترسی privilégiated و unprivileged روی همان نود به اشتراک گذاشته می‌شود). نسخه v1.36 زمان مناسبی است تا کلاستر خود را بررسی کنید و یا تنظیمات مورد نیاز را اعمال یا opt-out کنید 😊

اگر روی نودهای شما SELinux فعال نیست، تغییری برای شما رخ نمی‌دهد: kubelet تمام منطق مربوط به SELinux را نادیده می‌گیرد زمانی که SELinux در کرنل در دسترس یا فعال نیست. در این حالت می‌توانید این مطلب را بی‌خیال شوید.

این مطلب ادامه و گسترش کار قبلی است که در پست Kubernetes 1.27: Efficient SELinux Relabeling (Beta) توضیح داده شده بود و آنجا feature gate مربوط به SELinuxMountReadWriteOncePod معرفی شد. مشکل اصلی همان است، اما اینجا همین رویکرد را روی همه نوع volumes تعمیم می‌دهیم.

مسئله چیست؟

سیستم‌های لینوکسی که SELinux فعال دارند از برچسب‌ها (labels) روی آبجکت‌ها (مثل فایل‌ها و سوکت‌ها) برای تصمیم‌گیری دسترسی استفاده می‌کنند. به‌طور تاریخی، container runtime برچسب SELinux را به یک Pod و تمام volumeهای آن اعمال می‌کرد و این برچسب را به‌صورت بازگشتی روی همه فایل‌هایی که برای کانتینرها قابل‌مشاهده‌اند تغییر می‌داد. اگر حجم فایل‌ها زیاد باشد یا volume روی یک فایل‌سیستم راه‌دور باشد، این عملیات می‌تواند زمان‌بر شود.

نکته مهم: اگر کانتینر از subPath یک volume استفاده کند، فقط آن subPath برچسب‌گذاری بازگشتی می‌شود. با این روش می‌توان دو Pod با SELinux label متفاوت را روی یک volume واحد در subPathهای مختلف استفاده کرد.

اگر Pod هیچ SELinux label‌ای در Kubernetes API نداشته باشد، container runtime به آن یک label تصادفی اختصاص می‌دهد تا فرار از کانتینر (container escape) نتواند به داده‌های کانتینرهای دیگر دسترسی پیدا کند. حتی در این حالت، runtime باز هم تمام volumeها را به‌صورت بازگشتی relabel می‌کند.

چه چیزی در Kubernetes بهبود یافته؟

هر جا که پشته (stack) از آن پشتیبانی کند، kubelet می‌تواند volume را با گزینه mount مثل -o context= نصب کند تا کرنل برچسب صحیح را روی همه inodes همان mount اعمال کند، بدون نیاز به پیمایش بازگشتی. این مسیر تحت کنترل feature gates است و نیاز دارد که مثلاً Pod مقدار کافی از SELinux label را افشا کند (مثل spec.securityContext.seLinuxOptions.level) و driver مربوط به volume هم داوطلب این روش شود (برای CSI باید در CSIDriver فیلد spec.seLinuxMount: true تنظیم شود).

مراحل rollout تا امروز:

ReadWriteOncePod volumes تحت feature gate به نام SELinuxMountReadWriteOncePod مدیریت شدند؛ این feature از v1.28 به‌صورت پیش‌فرض فعال بود و در v1.36 به GA رسید.

پوشش وسیع‌تر تحت feature gate کلی SELinuxMount و همراه با فیلد spec.securityContext.seLinuxChangePolicy روی Podها مدیریت شده است.

اگر یک Pod و volume آن تمام شرایط زیر را داشته باشند، Kubernetes volume را مستقیماً با SELinux label درست mount می‌کند. این نوع mount در زمان ثابت انجام می‌شود و container runtime نیازی به relabel بازگشتی فایل‌ها نخواهد داشت. شرط‌ها عبارتند از:

– سیستم عامل باید از SELinux پشتیبانی کند. اگر SELinux شناسایی نشود، kubelet و container runtime هیچ کاری در مورد SELinux انجام نمی‌دهند.

– feature gate SELinuxMountReadWriteOncePod باید فعال باشد. در Kubernetes v1.36 این feature به صورت unconditional فعال است.

– Pod باید از یک PersistentVolumeClaim با accessModes مناسب استفاده کند: یا volume دارای accessModes: [“ReadWriteOncePod”] باشد، یا volume با access modeهای دیگر هم قابل‌استفاده باشد، مشروط بر اینکه feature gates SELinuxChangePolicy و SELinuxMount هر دو فعال باشند و Pod فیلد spec.securityContext.seLinuxChangePolicy را به nil (پیش‌فرض) یا MountOption تنظیم کرده باشد.

– Pod باید حداقل seLinuxOptions.level را در security context داشته باشد یا تمام کانتینرهای آن سطح را در سطح کانتینر ست کرده باشند. Kubernetes بقیه مقادیر user/role/type را از مقدارهای پیش‌فرض سیستم می‌خواند (معمولاً system_u، system_r و container_t). بدون دانستن حداقل level، container runtime بعد از mount سطح تصادفی اختصاص می‌دهد و باز هم relabel بازگشتی انجام می‌شود.

– volume plugin یا CSI driver مربوطه باید از mount با SELinux mount options پشتیبانی کند. پلاگین‌های درون‌هسته‌ای که این را پشتیبانی می‌کنند شامل fc و iscsi هستند. در CSI، در CSIDriver باید seLinuxMount: true تنظیم شود. هر volume یا driver دیگری که این فیلد را نگذارد، توسط container runtime به‌صورت بازگشتی relabel خواهد شد.

تغییرات شکاف‌ساز (breaking change)

فعال‌شدن SELinuxMount رفتار اشتراک‌گذاری volumes بین چند Pod را به‌طور ظریف تغییر می‌دهد. دو حالت که با relabel بازگشتی کار می‌کردند:

1) دو Pod با SELinux label متفاوت، یک volume مشترک دارند ولی هر کدام از subPath متفاوتی استفاده می‌کنند.

2) یک Pod privileged و یک Pod unprivileged همان volume را به اشتراک می‌گذارند.

در رفتار مدرن mount وقتی SELinux فعال است، این سناریوها کار نخواهند کرد: یکی از Podها ممکن است در وضعیت ContainerCreating گیر کند تا زمانی که Pod دیگر terminate شود. حالت اول نادر است، اما حالت دوم در برخی اپلیکیشن‌ها مشاهده شده است. نسخه v1.36 متریک‌ها و رخدادهایی را فراهم می‌کند تا این Podها را شناسایی کنید و به مدیران کلاستر اجازه می‌دهد با فیلد spec.securityContext.seLinuxChangePolicy از این رفتار opt-out کنند.

فیلد جدید Pod: spec.securityContext.seLinuxChangePolicy

این فیلد مشخص می‌کند SELinux label چگونه روی همه volumeهای Pod اعمال شود. در v1.36 این فیلد در Pod API پایدار است. سه انتخاب وجود دارد:

– فیلد تنظیم‌نشده (پیش‌فرض): در v1.36 رفتار بسته به فعال بودن یا نبودن SELinuxMount است. به‌صورت پیش‌فرض این feature غیرفعال است و برچسب به‌صورت recursive اعمال می‌شود. اگر feature را فعال کنید و بقیه شرط‌ها برقرار باشد، labeling با mount option انجام خواهد شد.

– Recursive: برچسب SELinux به‌صورت بازگشتی اعمال می‌شود. این حالت opt-out از mount option است.

– MountOption: برچسب با استفاده از mount option اعمال می‌شود، در صورتی که سایر شروط برآورده شود. این انتخاب فقط وقتی در دسترس است که SELinuxMount فعال باشد.

کنترلر هشدار SELinux (اختیاری)

در v1.36 کنترلری به نام selinux-warning-controller داخل kube-controller-manager اضافه شده است. برای استفاده از آن باید –controllers=*,selinux-warning-controller را در خط فرمان kube-controller-manager قرار دهید و همچنین نباید صراحتاً feature gate SELinuxChangePolicy را غیرفعال کرده باشید.

این کنترلر تمام Podهای کلاستر را زیر نظر می‌گیرد و وقتی دو Pod را پیدا کند که به روشی با هم در تضاد اند (یعنی اشتراکی که با SELinuxMount ناسازگار است)، یک Event تولید می‌کند. مثال رویداد:

SELinuxLabel “system_u:system_r:container_t:s0:c98,c99” conflicts with pod my-other-pod that uses the same volume as this pod with SELinuxLabel “system_u:system_r:container_t:s0:c0,c1”. If both pods land on the same node, only one of them may access the volume.

برای جلوگیری از نشت اطلاعات بین namespaceها، نام Pod ممکن است سانسور شود اگر Podهای متضاد در namespaceهای متفاوت باشند. کنترلر حتی وقتی Podها فعلاً روی یک نود نیستند هم رخداد را گزارش می‌کند تا تضمین شود Podها صرف‌نظر از تصمیم scheduler آتی، کار خواهند کرد.

علاوه بر Eventها، کنترلر متریکی به نام selinux_warning_controller_selinux_volume_conflict منتشر می‌کند که تمام تضادهای جاری بین Podها را فهرست می‌کند و برچسب‌های مربوط به Podها و SELinux labelهایشان را دارد. توجه کنید که این متریک ممکن است namespaceها را افشا کند، بنابراین فرض پروژه این است که فقط مدیران کلاستر به متریک‌های kube-controller-manager دسترسی دارند.

مسیر پیشنهادی برای آپگرید

برای داشتن آپگرید نرم از v1.36 به نسخه‌ای که SELinuxMount به‌طور پیش‌فرض فعال است (پیش‌بینی v1.37)، پیشنهاد می‌شود این مراحل را دنبال کنید:

1) selinux-warning-controller را در kube-controller-manager فعال کنید.

2) متریک selinux_warning_controller_selinux_volume_conflict را بررسی کنید. این متریک تمام تضادهای احتمالی بین Podها را نشان می‌دهد. برای هر Pod در تضاد (مثل Deployment یا StatefulSet)، یا opt-out کنید (فیلد Pod را روی spec.securityContext.seLinuxChangePolicy: Recursive تنظیم کنید) یا معماری اپلیکیشن را تغییر دهید تا تضاد از بین برود — مثلاً بررسی کنید که آیا واقعاً نیاز به اجرا به‌صورت privileged وجود دارد یا نه.

3) متریک volume_manager_selinux_volume_context_mismatch_warnings_total را چک کنید. این متریک از kubelet صادر می‌شود وقتی عملاً Pod را شروع می‌کند در حالتی که SELinuxMount غیرفعال است، اما آن Pod در زمان فعال شدن SELinuxMount ممکن است دیگر شروع نشود.

در کل، اگر از قبل به این نکات توجه کنید و موارد ناسازگار را یا با opt-out حل کنید یا طراحی را اصلاح کنید، وقتی SELinuxMount در نسخه بعدی به‌طور پیش‌فرض فعال شد، با مشکلات کمتری مواجه خواهید شد. اگر نیاز به کمک در تشخیص تضادها یا انتخاب استراتژی opt-out/ری‌آرکیتکچر دارید، خوشحال می‌شوم راهنمایی دقیق‌تری بدهم 🙂