چرا این موضوع مهم است؟
SELinux یک لایهٔ Mandatory Access Control است که فراتر از permissionهای سنتی Unix تصمیم میگیرد یک process با چه domainی به چه typeای دسترسی داشته باشد. Type Enforcement هستهٔ مدل عملیاتی آن است.
در یک سیستم واقعی، قبل از انتخاب ابزار باید مدل ذهنی درستی از لایهها داشته باشید. هر بار که یک مشکل را فقط با یک command یا تنظیم حفظی حل میکنیم، دانش به نسخه و محیط خاص گره میخورد. روش بهتر این است که ورودی، state، policy، مسیر داده و خروجی را مشخص کنیم. بعد ابزار را برای مشاهدهٔ یکی از این لایهها انتخاب کنیم. این رویکرد باعث میشود وقتی نسخه یا توزیع عوض شد، هنوز بدانیم دنبال چه evidenceای هستیم.
مدل معماری
در موضوع کنترل دسترسی اجباری SELinux و عیبیابی denialها، componentها را روی یک مسیر ساده بکشید و مرز مسئولیت هرکدام را مشخص کنید. state پایدار، state runtime، dependency بیرونی و نقطهٔ اعمال policy را جدا کنید. وقتی diagram ساده دارید، خطا از یک «سیستم بزرگ و مبهم» به چند مرحلهٔ قابل آزمون تبدیل میشود.
روش عملی کار
هر denial را evidence بدانید. ausearch، journal، ls -Z، restorecon، semanage و booleanها ابزارهای اصلیاند. اول context و policy موجود را بررسی کنید؛ audit2allow نباید اولین واکنش باشد.
برای Production، تغییر باید قابل اندازهگیری و قابل برگشت باشد. baseline قبل از تغییر شامل نسخه، config مؤثر، log سالم، metric اصلی و یک تست end-to-end است. rollout را با canary شروع کنید؛ بعد از هر مرحله هم تست مثبت و هم تست منفی داشته باشید. اگر success criterion از قبل نوشته نشده باشد، بعد از اجرا بهسادگی هر نتیجهای را میتوان «موفق» تفسیر کرد. rollback هم باید تمرین شود، نه اینکه فقط یک دستور روی کاغذ باشد.
از دید امنیت
خاموشکردن SELinux مشکل را حل نمیکند؛ کنترل را حذف میکند. تولید policy محلی از log بدون فهم علت نیز ممکن است دسترسی بیش از حد بدهد.
از دید امنیت، کمکردن سطح دسترسی مهم است اما availability و قابلیت عیبیابی هم جزو امنیت عملیاتیاند. secretها، tokenها، capabilityها، network path و logهای حساس را جدا بررسی کنید. هر exception باید owner، دلیل و تاریخ بازبینی داشته باشد. کنترلهایی که هیچکس نمیتواند اثرشان را مشاهده یا علتشان را توضیح دهد، در زمان incident به مانع تبدیل میشوند.
آزمایشگاه پیشنهادی
یک web service را عمداً روی path با context اشتباه قرار دهید، AVC را مشاهده کنید، با restorecon یا semanage context را اصلاح کنید و بدون خروج از Enforcing سرویس را برگردانید.
برای هر مرحله timestamp و خروجی را ذخیره کنید. یک failure کنترلشده ایجاد کنید تا مطمئن شوید monitoring واقعاً failure را میبیند. سپس سیستم را به baseline برگردانید و زمان recovery را ثبت کنید.
عیبیابی بر پایهٔ Evidence
Troubleshooting را با تغییر config شروع نکنید. اول محل دقیق failure را پیدا کنید: آیا ورودی وارد سیستم میشود؟ آیا state مورد انتظار ساخته شده؟ آیا policy اجازه میدهد؟ آیا خروجی از component خارج میشود؟ log، packet capture، metric و state API باید به یک timeline مشترک وصل شوند. نبودن event نیز evidence است؛ اگر request قبل از یک component دیده میشود و بعد از آن نه، دامنهٔ مسئله کوچک شده است.
Performance و ظرفیت
عملکرد را فقط با average نسنجید. p95/p99 latency، saturation، queue depth، retry و resource pressure تصویر بهتری میدهند. تغییری که در lab کوچک سریع است ممکن است زیر concurrency یا dataset واقعی bottleneck متفاوتی نشان دهد. capacity limitها را قبل از rollout بزرگ شناسایی کنید و alert را به thresholdهای قابل اقدام وصل کنید.
Production readiness
قبل از rollout نهایی، owner، change window، blast radius، success metric و rollback trigger باید مشخص باشند. اگر dependency خارجی دارید، رفتار هنگام timeout یا unavailable شدن آن را تست کنید. اگر config به چند host میرود، rollout مرحلهای و inventory دقیق لازم است. یک تغییر خوب باید observable، explainable و reversible باشد.
Runbook و انتقال دانش
در مستندسازی، فقط «چه دستورهایی اجرا شد» را ننویسید. سؤال، فرضیه، evidence مورد انتظار، نتیجه و دلیل تصمیم را ثبت کنید. اگر همکار دیگری بتواند با همان ورودی آزمایش را تکرار کند و به نتیجهٔ مشابه برسد، runbook ارزش عملیاتی دارد. اگر فقط نویسنده میداند چرا مرحلهای لازم است، دانش هنوز منتقل نشده است.
قبل از Production چه چیزی را اعتبارسنجی کنیم؟
نتیجهٔ Lab را مستقیم به Production تعمیم ندهید. یک change plan کوتاه بسازید که در آن دامنهٔ تغییر، owner، زمان اجرا، dependencyها، success metric و rollback trigger مشخص باشد. ابتدا روی canary یا درصد کوچکی از workload اجرا کنید و حداقل یک چرخهٔ واقعی restart، credential rotation یا deploy دوم را ببینید؛ بسیاری از خطاها در اولین اجرای سالم ظاهر نمیشوند. رفتار هنگام unavailable شدن dependency خارجی، پر شدن disk، timeout شبکه و ورودی نامعتبر را نیز بررسی کنید. اگر feature امنیتی است، test منفی باید نشان دهد مسیر غیرمجاز واقعاً بسته است؛ اگر feature performance است، benchmark باید با load و dataset نزدیک به واقعیت اجرا شود. در پایان، تصمیم rollout را با evidence ثبت کنید، نه با جملهٔ مبهم «بهنظر خوب است».
نگهداری بلندمدت و بازبینی دورهای
معماری بعد از deploy ثابت نمیماند. نسخهٔ سیستمعامل، dependency، policy سازمان، certificate، endpoint و حجم workload تغییر میکنند. برای همین باید بازبینی دورهای تعریف شود: نسخههای پشتیبانینشده را پیدا کنید، exceptionهای قدیمی را ببندید، alertهای بیارزش را حذف کنید و recovery را دوباره تمرین کنید. اگر configuration در Git نگهداری میشود، drift میان state واقعی و repository را اندازه بگیرید. اگر ابزار SaaS یا سرویس خارجی است، تغییر API، pricing، limit و مدل permission را دنبال کنید. مستندات باید تاریخ review داشته باشند تا تیم بداند کدام فرضها هنوز معتبرند. نگهداری خوب یعنی سیستم بعد از شش ماه هم قابل توضیح و قابل بازیابی باشد، نه اینکه فقط روز اول deploy موفق بوده باشد.
جمعبندی
هدف این مقاله دربارهٔ کنترل دسترسی اجباری SELinux و عیبیابی denialها ارائهٔ یک دستور جادویی نیست؛ هدف ساختن مدل ذهنی است. وقتی معماری را بفهمید، baseline داشته باشید و تغییر را با evidence کوچک کنید، ابزارها قابل تعویض میشوند اما روش حل مسئله باقی میماند. منبع اصلی این مقاله از Red Hat انتخاب شده تا جزئیات فنی قابل پیگیری باشند.
چکلیست سریع
- مسئله را در یک جمله تعریف کن.
- component و مرز اعتماد را مشخص کن.
- baseline را قبل از تغییر ذخیره کن.
- یک test مثبت و یک test منفی بساز.
- تغییر را با canary شروع کن.
- evidence را timestampدار نگه دار.
- rollback را واقعاً تمرین کن.
- نتیجه و exceptionها را در runbook ثبت کن.
خواندن یک AVC به زبان عملی
در گزارش AVC ابتدا نام فرایند را از مجوز ردشده جدا کنید. نام برنامه توضیح میدهد چه کسی عملیات را آغاز کرده، اما تصمیم SELinux بر مبنای context مبدأ و مقصد و کلاس شیء گرفته میشود. برای مثال خواندن یک فایل و وصل شدن به یک پورت شبکه دو درخواست متفاوتاند. اگر فقط عبارت permission denied را ببینید و فوراً مالک فایل را عوض کنید، ممکن است اصلاً لایه مسئول را تغییر نداده باشید. زمان رخداد، شناسه فرایند، مسیر فایل، عملیات و contextها را کنار یک درخواست قابل تکرار ثبت کنید تا گزارش مربوط به اجرای دیگری وارد تحلیل نشود.
مالکیت و mode معمول یونیکس همچنان اهمیت دارند. مجاز بودن در SELinux به معنی عبور از کنترل دسترسی معمول نیست و مجاز بودن در کنترل معمول نیز تضمین عبور از SELinux نیست. برای یک سرویس وب، ابتدا ثابت کنید کاربر سرویس مسیر را میپیماید و فایل را طبق مجوزهای معمول میخواند. سپس برچسب فایل و domain فرایند را بررسی کنید. این ترتیب از ساختن policy برای مشکلی که در واقع ناشی از مجوز یک پوشه والد است جلوگیری میکند. خروجی ls -Z برای فایل و ps -Z برای فرایند دو شاهد مکملاند، نه دو نمایش یک چیز.
برچسب پایدار در برابر تغییر موقت
فرض کنید محتوای وب از مسیر معمول به یک دیسک تازه منتقل شده است. فایلهایی که روی مقصد ساخته شدهاند ممکن است برچسب مناسب سرویس وب را نداشته باشند. تغییر مستقیم context میتواند در آزمایش علت را روشن کند، اما اگر نگاشت دائمی مسیر ثبت نشده باشد، بازبرچسبگذاری بعدی نتیجه را برمیگرداند. ابتدا با ابزارهای policy نگاشت موجود را بخوانید، سپس برای مسیر اختصاصی نگاشت درست تعریف کنید و آن را با restorecon اعمال کنید. انتخاب type باید از policy سرویس و نیاز خواندن یا نوشتن همان پوشه بیاید؛ همه محتوای سایت نباید فقط برای راحتی قابل نوشتن شود.
یک پوشه بارگذاری فایل را از پوشه فایلهای اجرایی و تنظیمات جدا کنید. سرویس ممکن است به اولی نیاز نوشتن داشته باشد، ولی دلیلی ندارد بتواند کد خود را تغییر دهد. بعد از اصلاح برچسب، هم خواندن صفحه عمومی و هم ناتوانی در نوشتن فایل تنظیمات را بررسی کنید. آزمایش منفی ارزش اصلاح را نشان میدهد. اگر پوشه روی فایلسیستمی قرار دارد که ویژگیهای لازم را ندارد، با تکرار restorecon مسئله حل نمیشود؛ قابلیت فایلسیستم، گزینههای mount و راهکار مستند همان محیط باید بررسی شوند.
Boolean، پورت و policy محلی
Boolean یک کلید برای رفتار تعریفشده در policy است و نباید مثل مجوز نامحدود تلقی شود. پیش از فعالکردن آن، توضیح گزینه و دامنه فرایندهای اثرپذیر را بخوانید. اگر وبسرور فقط به یک پایگاه داده نیاز دارد، بررسی کنید گزینه انتخابی چه نوع اتصالهایی را باز میکند. برای سرویس روی پورت غیرمعمول نیز فقط فایروال مطرح نیست؛ نگاشت پورت به type میتواند عامل رد شدن bind باشد. باز بودن پورت در شبکه، حق گوشدادن فرایند روی آن را تضمین نمیکند.
policy محلی زمانی معنا دارد که برچسب درست است، تنظیمات استاندارد کافی نیست و نیاز برنامه واقعاً مشروع است. خروجی پیشنهاددهندههایی مثل audit2allow را متن قابل بازبینی بدانید، نه نسخه آماده اجرا. یک log ممکن است رفتار ناخواسته یا فعالیت مهاجم را هم در خود داشته باشد. هر قانون را به یک نیاز کاربردی وصل کنید، عملیات اضافی را حذف کنید و بسته policy را همراه نمونه آزمون نگه دارید. هنگام ارتقای بسته اصلی، دوباره بررسی کنید آیا policy توزیع نیاز را پوشش میدهد تا استثنای قدیمی بیدلیل باقی نماند.
در پایان آزمایش، وضعیت Enforcing را صریحاً کنترل کنید. موفق شدن درخواست در حالتی که تنها گزارشگیری فعال است، اثبات درست بودن policy اجرایی نیست. گزارش نهایی باید نشان دهد کدام درخواست مجاز موفق شده، کدام درخواست خارج از دامنه رد شده و کدام نگاشت یا Boolean دقیقاً تغییر کرده است. این سه شاهد برای همکار بعدی بسیار ارزشمندتر از مجموعهای از فرمانهای بدون توضیح هستند.