اجرای سرویسها با کاربر غیر ریشه در لینوکس
اجرای سرویس با دسترسی root امنیت سرور را به خطر میاندازد. با استفاده از قابلیت DynamicUser در systemd یا ایجاد حساب کاربری محدود، سطح دسترسی مهاجمان را به حداقل برسانید.
چرا همه چیز را با کاربر root اجرا نکنیم
کاربر root میتواند هر کاری در سیستم انجام دهد: تمام فایلها را بخواند، هر تنظیمی را تغییر دهد و کل سیستم را حذف کند. وقتی سرویسی را با دسترسی root اجرا میکنید، تمام این قدرت را در اختیار آن سرویس قرار میدهید. اگر آن سرویس دارای باگی باشد که مهاجم بتواند از آن سوءاستفاده کند، مهاجم فقط به آن سرویس دسترسی پیدا نمیکند، بلکه به سطح دسترسی root میرسد و root یعنی کل سرور. اجرای سرویس با یک کاربر فاقد دسترسی (unprivileged)، خسارت احتمالی را محدود میکند. باگی در سرویسی که با یک حساب کاربری محدود اجرا میشود، به مهاجم تنها دسترسیهایی را میدهد که آن حساب کاربری دارد؛ که در حالت ایدهآل باید تقریباً هیچ باشد.
این همان اصل حداقل دسترسی (principle of least privilege) است: به هر بخش از سیستم دقیقاً همان دسترسیهایی را بدهید که برای انجام وظیفهاش نیاز دارد و نه بیشتر. این مؤثرترین عادت برای محدود کردن دامنه آسیب در صورت نفوذ (blast radius) است و در سرورهای مدرن، اعمال آن تقریباً هیچ هزینهای برای شما ندارد.
ایجاد یک حساب کاربری اختصاصی برای هر سرویس
رویکرد کلاسیک، ایجاد یک کاربر سیستمی مجزا برای هر سرویس است؛ کاربری که تنها مالک فایلهای همان سرویس باشد و امکان ورود به سیستم (login) نداشته باشد. یک حساب سیستمی برای یک برنامه وب ممکن است به این شکل باشد:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin appsvcهر پرچم (flag) اهمیت دارد. --system آن را به یک حساب سرویس تبدیل میکند، نه یک حساب کاربری انسانی. --no-create-home از ایجاد دایرکتوری خانگی (home directory) که به آن نیازی نیست، صرفنظر میکند. --shell /usr/sbin/nologin به این معناست که حتی اگر مهاجم به نحوی به این حساب دسترسی پیدا کند، نمیتواند با آن یک shell باز کند. این حساب تنها برای مالکیت یک پردازش و فایلهای آن وجود دارد.
سپس، دسترسیهای لازم را تنها به همان فایلهایی که سرویس نیاز دارد محدود کنید و نه بیشتر:
sudo chown -R appsvc:appsvc /opt/myappاکنون سرویس فقط دایرکتوری خود را میخواند و در آن مینویسد و در هیچ جای دیگری از دیسک دسترسی ندارد. اگر روزی این سرویس مورد نفوذ قرار گیرد، فایلهایی که مهاجم میتواند تغییر دهد محدود به /opt/myapp است؛ حساب کاربری همچنان میتواند هر فایلی که برای همه قابل خواندن است را بخواند، اما قادر به تغییر سایر بخشهای سیستم نخواهد بود.
اجازه دهید systemd سرویس را با آن کاربر اجرا کند
پس از ایجاد حساب کاربری، به systemd دستور دهید که سرویس را با همان کاربر اجرا کند. در فایل unit، این کار با یک خط انجام میشود:
[Service]
ExecStart=/opt/myapp/bin/server
User=appsvc
Group=appsvcUser=appsvc باعث میشود پردازش بهجای root، با دسترسیهای محدود آن حساب کاربری آغاز شود. این روش استاندارد و متداولی برای اجرای یک برنامه تحت systemd است و ارزش آن را دارد که برای هر سرویسی که برای آن unit مینویسید، انجام شود. با این حال، اگر systemd در حال نظارت بر پردازش اشتباهی باشد، کاهش سطح دسترسی (privilege dropping) کمکی نخواهد کرد؛ بنابراین اگر پس از خروج بیسروصدای daemon، وضعیت unit همچنان active گزارش میشود، بررسی کنید که نوع Type= مناسب را برای نحوه شروع پردازش خود انتخاب کرده باشید.
یا با استفاده از DynamicUser، کلاً از حساب کاربری صرفنظر کنید
systemd میتواند یک گام فراتر برود و یک کاربر موقت برای شما ایجاد کند؛ کاربری که تنها در زمان اجرای سرویس وجود دارد. کافی است DynamicUser=yes را تنظیم کنید تا دیگر نیازی به مدیریت هیچ حساب کاربری نداشته باشید:
[Service]
ExecStart=/opt/myapp/bin/server
DynamicUser=yes
StateDirectory=myappدر زمان شروع، systemd یک شناسه کاربری (UID) بلااستفاده اختصاص میدهد و در زمان توقف، آن را آزاد میکند. این سرویس همچنین یک /tmp خصوصی، یک نمای فقطخواندنی از اکثر بخشهای فایلسیستم، و یک دایرکتوری وضعیت با قابلیت نوشتن در مسیر /var/lib/myapp دریافت میکند که StateDirectory= آن را آماده کرده و در اختیار سرویس قرار میدهد. هیچکدام از این قابلیتها در SysV init وجود نداشت؛ در آنجا سلب دسترسیها (dropping privileges) به عهده اسکریپت راهاندازی هر سرویس بود. همین شکاف امنیتی، بخش بزرگی از دلیل اصلی مهاجرت توزیعها به systemd است. برای یک سرویس خودکفا که تنها به دایرکتوری وضعیت اختصاصی خود نیاز دارد، DynamicUser=yes کمدردسرترین روش برای دستیابی به ایزولاسیون قوی است، زیرا هیچ حساب کاربری پایداری وجود ندارد که مهاجم بتواند آن را هدف قرار دهد.
نوشتن دستی فایلهای unit کاری دقیق و حساس است و بخش عمده ارزش کار در تنظیم صحیح دستورالعملهای امنسازی (hardening) نهفته است. ابزار تولیدکننده در راهنمای سرویس و تایمر systemd میتواند این گزینهها را برای شما تکمیل کند تا فایل unit از همان ابتدا به درستی پیکربندی شود.
نحوهٔ جایگیری این موضوع در کنار سایر موارد
اصل حداقل دسترسی (Least privilege) تنها یک لایه است و بهجای جایگزینی سایر لایهها، در کنار آنها عمل میکند. یک فایروال با سیاست پیشفرض مسدودسازی (default-deny) کنترل میکند که چه چیزی میتواند به سرویس دسترسی پیدا کند؛ اجرای سرویس با یک کاربر بدون امتیاز (unprivileged user) کنترل میکند که در صورت نفوذ به سرویس، آن سرویس چه کاری میتواند انجام دهد؛ و امنسازی SSH (hardened SSH) از همان ابتدا مانع ورود مهاجمان به سرور میشود. هیچکدام از این موارد بهتنهایی کافی نیستند و در کنار هم باعث میشوند که یک باگ در یک سرویس، منجر به نفوذ به کل سرور نشود. میزبانی سرویسی که از اسرار محافظت میکند، نشان میدهد که این لایهها کجا متوقف میشوند: یک حساب کاربری محدود، دسترسیهای یک فرایندِ نفوذشده را محدود میکند، اما یک مدیریتکننده رمز عبور خودمیزبان مانند Vaultwarden همچنان به نحوهٔ محافظت شما از توکن مدیریتی و فایل پشتیبان آن وابسته است؛ مواردی که ایزولهسازی کاربر آنها را پوشش نمیدهد.
پیش از ادامه، یک چکلیست امنسازی برای کل سرور اجرا کنید و یک نسخهٔ شخصیسازیشده برای کار خود ایجاد نمایید:
FAQ
چرا نباید یک سرویس را با کاربر root اجرا کنم؟
چون کاربر root دسترسی نامحدود به کل ماشین دارد، اگر سرویسی که با این دسترسی اجرا شده مورد نفوذ قرار گیرد، مهاجم به کل سرور دسترسی پیدا میکند، نه فقط به همان سرویس. اجرای سرویس با یک حساب کاربری محدود و بدون امتیاز، خسارت را تنها به منابعی محدود میکند که آن حساب به آنها دسترسی دارد. دسترسی root را برای کارهای مدیریتی حفظ کنید و هر سرویسی که بهطور مداوم در حال اجراست را با یک کاربر محدودشده اجرا نمایید.
چگونه کاربری بسازم که نتواند وارد سیستم شود؟
دستور sudo useradd --system --no-create-home --shell /usr/sbin/nologin NAME را اجرا کنید. پوسته nologin به این معناست که این حساب حتی در صورت سرقت اعتبارنامهها، نمیتواند یک نشست تعاملی باز کند، --system آن را به عنوان یک حساب سرویس علامتگذاری میکند و --no-create-home از ایجاد دایرکتوری خانگی که به آن نیازی ندارد، صرفنظر میکند. با استفاده از chown، مالکیت فایلهای اختصاصیاش را به خودِ آن کاربر بدهید.
قابلیت DynamicUser در systemd چیست؟
DynamicUser=yes به systemd دستور میدهد که یک کاربر موقت برای سرویس ایجاد کند که فقط در زمان اجرای سرویس وجود دارد، بنابراین نیازی به مدیریت یک حساب کاربری دائمی نخواهید داشت. این قابلیت همچنین یک /tmp خصوصی، یک نمای فایلسیستم عمدتاً فقطخواندنی و یک دایرکتوری وضعیت مدیریتشده به سرویس میدهد. این کمهزینهترین روش برای اجرای یک سرویس مستقل با یک هویت یکبارمصرف و دارای حداقل امتیاز است.
آیا اجرای سرویس با کاربری غیر از root جایگزین فایروال میشود؟
خیر. این دو از موارد متفاوتی محافظت میکنند. اجرای سرویس با یک کاربر بدون امتیاز، کارهایی که سرویس در صورت نفوذ میتواند انجام دهد را محدود میکند، در حالی که فایروال محدود میکند که چه چیزی اصلاً میتواند به سرویس دسترسی پیدا کند. از هر دو، در کنار SSH ایمنشده استفاده کنید تا هر لایه، خلأهای لایههای دیگر را پوشش دهد.
سرویس باید مالک چه فایلهایی باشد؟
فقط فایلهایی که سرویس واقعاً به آنها نیاز دارد و نه بیشتر. مالکیت دایرکتوری کاری و دادههای سرویس را به همان حساب کاربری بدهید و مالکیت بقیه فایلها را نزد root باقی بگذارید. یک الگوی مناسب، استفاده از sudo chown -R svc-app:svc-app /opt/svc-app برای دایرکتوری برنامه است، در حالی که فایلهای پیکربندی در /etc تحت مالکیت root باقی میمانند و فقط برای سرویس قابل خواندن هستند. هدف این است که اگر فرآیند مورد نفوذ قرار گرفت، فایلهایی که میتواند تغییر دهد به دادههای خودش محدود باشد، نه کل سیستم.