اجرای سرویسها با کاربر unprivileged
اجرای سرویس با root باعث دسترسی کامل مهاجم به سرور میشود. برای امنیت بیشتر از حسابهای اختصاصی یا قابلیت DynamicUser در systemd استفاده کنید.
چرا نباید همه چیز را با root اجرا کرد
کاربر root میتواند هر کاری در سیستم انجام دهد: خواندن هر فایلی، تغییر هر تنظیمی و حذف کل سیستم. وقتی سرویسی را با root اجرا میکنید، تمام آن قدرت را به آن سرویس واگذار میکنید. اگر سرویس دارای باگی باشد که مهاجم بتواند از آن سوءاستفاده کند، او فقط به آن سرویس دسترسی پیدا نمیکند، بلکه دسترسی root را به دست میآورد و root یعنی کل سرور. اجرا با یک کاربر فاقد امتیاز (unprivileged user) میزان آسیب را محدود میکند. یک باگ در سرویسی که با یک حساب کاربری محدود اجرا میشود، به مهاجم فقط چیزهایی را میدهد که آن حساب مجاز به دسترسی به آنهاست، که باید تقریباً هیچ باشد.
این اصل «کمترین امتیاز» (least privilege) است: به هر بخش از سیستم دقیقاً همان دسترسیای را بدهید که برای انجام وظیفهاش نیاز دارد و نه بیشتر. این موثرترین عادت برای محدود کردن شعاع انفجار (blast radius) یک نفوذ است و در یک سرور مدرن، پیادهسازی آن تقریباً هیچ هزینهای ندارد.
یک حساب کاربری اختصاصی برای هر سرویس
روش کلاسیک، ایجاد یک کاربر سیستمی مجزا برای هر سرویس است؛ کاربری که فقط مالک فایلهای همان سرویس باشد و نتواند وارد سیستم (login) شود. یک حساب سیستمی برای یک وباپلیکیشن ممکن است به این صورت باشد:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin appsvcهر پرچم (flag) اهمیت دارد. --system آن را به یک حساب سرویس تبدیل میکند، نه یک حساب کاربر انسانی. --no-create-home از ایجاد پوشه home که نیازی به آن نیست جلوگیری میکند. --shell /usr/sbin/nologin به این معنی است که حتی اگر مهاجم به هر نحوی به حساب دسترسی پیدا کند، نمیتواند با آن یک shell باز کند. این حساب فقط برای مالکیت یک پروسس و فایلهای آن وجود دارد.
سپس فقط فایلهایی را که آن کاربر نیاز دارد به او بدهید و نه بیشتر:
sudo chown -R appsvc:appsvc /opt/myappحالا سرویس پوشه خود را میخواند و مینویسد و هیچ ارتباطی با هیچ جای دیگری در دیسک ندارد. اگر سرویس مورد سوءاستفاده قرار گیرد، فایلهایی که مهاجم میتواند تغییر دهد محدود به /opt/myapp است؛ حساب کاربری همچنان میتواند هر چیزی که برای همه قابل خواندن (world-readable) است را بخواند، اما نمیتواند بقیه سیستم را تغییر دهد.
اجازه دهید systemd آن را با آن کاربر اجرا کند
پس از ایجاد حساب کاربری، به systemd بگویید سرویس را با آن اجرا کند. در فایل unit، یک خط این کار را انجام میدهد:
[Service]
ExecStart=/opt/myapp/bin/server
User=appsvc
Group=appsvcUser=appsvc به این معنی است که پروسس به جای root، با امتیازات محدود آن حساب کاربری شروع به کار میکند. این روش استاندارد و رایج برای اجرای یک اپلیکیشن تحت systemd است و ارزش انجام دادن برای هر سرویسی که برای آن unit مینویسید را دارد.
یا با استفاده از DynamicUser از حساب کاربری صرفنظر کنید
systemd میتواند یک قدم فراتر برود و یک کاربر موقت برای شما بسازد، کاربری که فقط تا زمانی که سرویس در حال اجراست وجود دارد. DynamicUser=yes را تنظیم کنید و دیگر نیازی به مدیریت هیچ حسابی نخواهید داشت:
[Service]
ExecStart=/opt/myapp/bin/server
DynamicUser=yes
StateDirectory=myappدر هنگام شروع، systemd یک User ID استفاده نشده اختصاص میدهد؛ در هنگام توقف، آن را آزاد میکند. سرویس همچنین یک /tmp خصوصی دریافت میکند (یک نمای فقطخواندنی از بیشتر فایلسیستم) و یک دایرکتوری state قابل نوشتن زیر /var/lib/myapp که توسط StateDirectory= ایجاد و به آن تحویل داده میشود. برای یک سرویس مستقل که فقط به دایرکتوری state خود نیاز دارد، DynamicUser=yes کمهزینهترین راه برای دستیابی به جداسازی قوی است، زیرا اصلاً حساب کاربری طولانیمدتی وجود ندارد که مهاجم بتواند آن را هدف قرار دهد.
نوشتن unitها به صورت دستی دشوار است و تنظیم صحیح دستورات امنیتی (hardening) بیشترین ارزش را دارد. generator موجود در راهنمای سرویس و timer در systemd میتواند این گزینهها را برای شما پر کند تا unit در اولین تلاش صحیح باشد.
این موضوع چگونه با بقیه موارد هماهنگ میشود
کمترین امتیاز تنها یک لایه است و به جای جایگزینی، با لایههای دیگر همکاری میکند. یک فایروال default-deny کنترل میکند که چه چیزی میتواند به سرویس برسد؛ اجرا با یک کاربر فاقد امتیاز کنترل میکند که سرویس در صورت نفوذ چه کاری میتواند انجام دهد؛ و SSH امنشده از همان ابتدا مانع ورود مهاجمان به سیستم میشود. هیچکدام از اینها به تنهایی کافی نیست، و در کنار هم به این معناست که یک باگ در یک سرویس تبدیل به نفوذ به کل سرور نمیشود.
قبل از ادامه، لیست بررسی امنیتی (hardening checklist) را برای کل سیستم اجرا کنید و یک نسخه شخصیسازی شده برای کار کردن ایجاد کنید:
FAQ
چرا نباید یک سرویس را با root اجرا کنم؟
چون root میتواند هر کاری در سیستم انجام دهد، سرویسی که با root اجرا میشود و مورد سوءاستفاده قرار میگیرد، تمام سرور را به مهاجم تحویل میدهد، نه فقط آن سرویس را. اجرای سرویس با یک حساب کاربری محدود و فاقد امتیاز، آسیب را به هر چیزی که آن حساب مجاز به دسترسی به آن است، محدود میکند. root را برای مدیریت رزرو کنید و هر سرویس با زمان اجرای طولانی را با یک کاربر محدود اجرا کنید.
چگونه کاربری بسازم که نتواند وارد سیستم شود؟
sudo useradd --system --no-create-home --shell /usr/sbin/nologin NAME را اجرا کنید. shell از نوع nologin به این معنی است که حساب کاربری حتی اگر اعتبارنامههای آن سرقت شود، نمیتواند یک session تعاملی باز کند، --system آن را به عنوان یک حساب سرویس علامتگذاری میکند، و --no-create-home از ایجاد پوشه home که نیازی به آن نیست جلوگیری میکند. با استفاده از chown، مالکیت فقط فایلهای خودش را به آن بدهید.
DynamicUser در systemd چیست؟
DynamicUser=yes به systemd میگوید که یک کاربر موقت برای سرویس بسازد که فقط تا زمان اجرای آن وجود دارد، بنابراین شما هرگز یک حساب کاربری طولانیمدت را مدیریت نمیکنید. این کار همچنین یک /tmp خصوصی (یک نمای فایلسیستم که عمدتاً فقطخواندنی است) و یک دایرکتوری state مدیریتشده به سرویس میدهد. این کمهزینهترین راه برای اجرای یک سرویس مستقل با یک هویت موقت و با امتیاز پایین است.
آیا اجرا با کاربر غیر-root جایگزین فایروال میشود؟
خیر. آنها از چیزهای متفاوتی محافظت میکنند. اجرا با یک کاربر فاقد امتیاز، کارهایی که یک سرویس در صورت نفوذ میتواند انجام دهد را محدود میکند، در حالی که یک فایروال محدود میکند که چه چیزی اصلاً میتواند به سرویس برسد. از هر دو، همراه با SSH امنشده، استفاده کنید تا هر لایه آنچه را که لایههای دیگر نمیتوانند پوشش دهند، پوشش دهد.
کاربر سرویس باید مالک چه فایلهایی باشد؟
فقط فایلهایی که سرویس واقعاً به آنها نیاز دارد و نه بیشتر. مالکیت دایرکتوری کاری و دادههای خود را به حساب کاربری بدهید و بقیه چیزها را تحت مالکیت root باقی بگذارید. یک الگوی خوب این است که از sudo chown -R svc-app:svc-app /opt/svc-app برای دایرکتوری اپلیکیشن استفاده کنید، در حالی که تنظیمات زیر /etc تحت مالکیت root باقی میماند و فقط توسط سرویس قابل خواندن است. هدف این است که اگر پروسس مورد نفوذ قرار گرفت، فایلهایی که میتواند تغییر دهد محدود به دادههای خودش باشد، نه بقیه سیستم.