SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

آموزش تنظیم و تغییر umask در لینوکس

با دستور umask سطح دسترسی فایل‌های جدید را مدیریت کنید. در این مطلب یاد می‌گیرید چگونه با دستور umask -S مقدار پیش‌فرض را مشاهده کرده و تفاوت آن در کاربر root و عادی را بررسی کنید.

عملکرد umask در لینوکس

مقدار umask عددی است که هر پردازش در لینوکس به همراه دارد و تعیین‌کننده سطح دسترسی (mode) هر فایل یا دایرکتوری است که آن پردازش ایجاد می‌کند. یک برنامه در زمان ایجاد فایل، مجموعه‌ای از مجوزها را از هسته (kernel) درخواست می‌کند. هسته تمام بیت‌هایی که در mask نام برده شده‌اند را پاک کرده و باقی‌مانده را اعمال می‌کند. umask هرگز دسترسی جدیدی اعطا نمی‌کند؛ بلکه فقط بیت‌هایی را از آنچه برنامه درخواست کرده است، حذف می‌کند.

این مقدار ویژگی توزیع (distribution) شما نیست. این مقدار به حسابی (account) که با آن وارد شده‌اید و نحوه شروع shell بستگی دارد. این دو پاسخ می‌توانند روی یک ماشین، در یک لحظه و روی یک image پیش‌فرض، متفاوت باشند. بنابراین گام نخست، جستجو در دفترچه راهنما نیست؛ بلکه اندازه‌گیری آن روی سیستمی است که مقابل شما قرار دارد.

نمایش umask در شل فعلی

umask
umask -S

شکل اول، ماسک را به صورت اکتال (هشت‌هشتی) نمایش می‌دهد. شکل دوم، همان ماسک را به صورت مجوزهایی که مجاز می‌شمارد و با فرمت نمادینی که chmod می‌پذیرد، چاپ می‌کند. هر دو خط را روی صفحه نگه دارید. تمام موارد زیر، مقایسه‌ای با خروجی شل شماست.

umask یک دستور داخلی (builtin) شل است و یک برنامه مستقل روی دیسک نیست. این موضوع را با type umask تأیید کنید. این نکته اهمیت دارد، زیرا یک دستور داخلی، خودِ پردازش شل را تغییر می‌دهد. یک برنامه مستقل فقط می‌تواند پردازش خودش را تغییر دهد و پس از پایان اجرا، تغییرات نیز از بین می‌روند.

ایجاد یک فایل و یک دایرکتوری، سپس خواندن مجوزها

cd "$(mktemp -d)"
touch probe.file
mkdir probe.dir
stat -c '%a %A %n' probe.file probe.dir

دستور %a مجوز را به صورت اکتال (octal) چاپ می‌کند و %A همان مجوز را با فرمت drwxr-xr-x که ls -l استفاده می‌کند، نمایش می‌دهد. هر دو خط را با ماسکی که لحظاتی پیش چاپ کردید مقایسه کنید. هر بیتی که در ماسک تنظیم شده باشد، در مجوزها وجود ندارد؛ زیرا تنها کار ماسک، پاک کردن آن بیت‌هاست. اگر خواندن ستون %A هنوز برایتان واضح نیست، رشته مجوز drwxr-xr-x اولین چیزی است که باید به درستی درک کنید.

فایل و دایرکتوری با یکدیگر تفاوت دارند و دلیل این تفاوت، ماسک نیست. دستور touch از هسته (kernel) درخواست دسترسی خواندن و نوشتن برای مالک، گروه و دیگران می‌کند. دستور mkdir درخواست خواندن، نوشتن و اجرا برای هر سه گروه دارد. ماسک یکسانی از دو درخواست متفاوت کسر می‌شود. بنابراین فایلی که توسط touch ایجاد شده است، هرگز قابل اجرا (executable) نخواهد بود، فارغ از اینکه ماسک چه مقداری داشته باشد: بیت اجرا هرگز درخواست نشده است و ماسک نمی‌تواند بیتی را به مجوزها اضافه کند.

( umask a=rwx; touch open.file; stat -c '%a %A %n' open.file )

آن پرانتزها دستورات را در یک subshell اجرا می‌کنند، بنابراین تغییرات پس از پایان آن از بین می‌روند. ماسک اکنون درخواست می‌کند که هیچ چیزی پاک نشود، اما stat همچنان گزارش می‌دهد که بیت اجرا روی فایل وجود ندارد. پس از آن دوباره umask را اجرا کنید تا مقدار اصلی شما بازگردد؛ این نشان می‌دهد که این تنظیمات درون یک پردازش (process) زندگی می‌کنند و توسط فرزندان به ارث می‌رسند، نه اینکه در جایی روی دیسک ذخیره شوند.

دایرکتوری‌ها جایی هستند که اثر پاک شدن بیت اجرا در آن‌ها حس می‌شود.

( umask a=rw; mkdir noexec.dir; cd noexec.dir )

به عنوان یک کاربر عادی، دستور cd با خطای bash: cd: noexec.dir: Permission denied مواجه می‌شود، زیرا ماسک، بیت اجرایی را که mkdir درخواست کرده بود پاک کرده است و دایرکتوری بدون بیت اجرا، قابل ورود نیست. کاربر root از این بررسی صرف‌نظر می‌کند، بنابراین این مورد فقط در یک حساب کاربری معمولی خود را نشان می‌دهد.

چرا root و کاربر شما umask متفاوتی می‌بینند

همان اندازه‌گیری را با یک حساب کاربری دیگر که به روشی متفاوت اجرا شده است انجام دهید و دو خروجی را در کنار یکدیگر بخوانید.

umask
sudo -i umask

sudo -i شل ورود (login shell) کاربر root را اجرا می‌کند و دستور داخلی را درون آن به کار می‌گیرد، بنابراین این یک حساب کاربری متفاوت است که از طریق مسیر راه‌اندازی متفاوتی وارد شده است. در ایمیج‌های پیش‌فرض سرور Ubuntu و Debian، این دو خط ممکن است مقادیر متفاوتی را چاپ کنند. هر دو خط صحیح هستند. هر کدام نشان می‌دهد که مسیر راه‌اندازی مربوط به خودش چه مقداری تولید کرده است، و ادامهٔ این مطلب دربارهٔ این است که کدام بخش از سیستم آن را تولید کرده است.

کدام فایل در image شما مقدار را تعیین کرده است

grep -nE '^(UMASK|USERGROUPS_ENAB)' /etc/login.defs
grep -rn pam_umask /etc/pam.d/
grep -rn umask /etc/profile /etc/profile.d/ /etc/bash.bashrc ~/.profile ~/.bashrc 2>/dev/null || echo 'no umask line in the shell startup files'

اگر دستور اول grep خروجی نداشت، آن را دوباره بدون anchor ^ اجرا کنید: ممکن است خط مورد نظر کامنت شده باشد و یک خط کامنت‌شده، مستندات محسوب می‌شود نه پیکربندی. دستور سوم grep همان موردی است که کاربران را غافلگیر می‌کند. در Debian و Ubuntu، فایل /etc/profile پیش‌فرض عمدتاً به PAM اشاره دارد و خودِ آن mask را تنظیم نمی‌کند؛ بنابراین فایلی که تصور می‌کردید مسئول است، اغلب مسئول نیست. دستور grep در صورت عدم تطابق با خروجی غیر صفر خارج می‌شود و به همین دلیل آن خط به || echo ختم شده است: در imageای که هیچ فایل startupای به mask اشاره نمی‌کند، شما به جای سکوت، پیام دریافت می‌کنید و همان پیام، یافتهٔ نهایی است.

/etc/login.defs advertises a value and PAM applies one

The UMASK line in /etc/login.defs is the value most guides quote. Neither the kernel nor the shell reads that file. It is read by pam_umask, a PAM (pluggable authentication modules) module that runs when a session is created. pam_umask takes the first value it finds: a umask= entry in the user's GECOS field, then a umask= argument written on the pam_umask.so line itself, then UMASK from /etc/login.defs. Distributions patch this module, so run man pam_umask on your own image and read the order printed there.

That is how /etc/login.defs can advertise one value while your session ends up with another, with no warning printed on either side. The greps tell you which case you are in. If the pam_umask.so line carries its own umask= argument, the login.defs line lost.

USERGROUPS_ENAB و استثنای root

id -un
id -gn

اگر این دو دستور نام یکسانی را چاپ کنند، شما یک user private group دارید: حساب کاربری با گروه اختصاصی خود که هم‌نام با آن است ایجاد شده است. ماژول pam_umask رفتاری به نام usergroups دارد که توسط USERGROUPS_ENAB در فایل /etc/login.defs کنترل می‌شود. هنگامی که این قابلیت فعال باشد و حساب کاربری root نباشد، و نام گروه اصلی با نام کاربر مطابقت داشته باشد، ماژول رقم مالک را در ماسک به رقم گروه کپی می‌کند. در نتیجه، نشست (session) با ماسکی پایان می‌یابد که بیت‌های گروه را برای هر فایلی که آن حساب ایجاد می‌کند، باز می‌گذارد. حساب root توسط خود ماژول مستثنی شده است و همین استثنا، رایج‌ترین دلیل برای این است که دو shell روی یک سرور، ماسک‌های متفاوتی را چاپ می‌کنند.

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

Login shell، non-login shell و non-interactive shell

umask
bash -lc 'umask'
bash -c 'umask'

هنگامی که یک نشست (session) ایجاد می‌شود، PAM اجرا می‌گردد: login در کنسول، sshd، su، sudo -i. زمانی که یک shell، یک shell دیگر را اجرا می‌کند، PAM اجرا نمی‌شود. bash -l یک login shell است، بنابراین فایل‌های /etc/profile و ~/.profile را می‌خواند، اما هرگز pam_umask را فراخوانی نمی‌کند، زیرا نشستی ایجاد نشده است. bash -c هیچ‌کدام از این فایل‌ها را نمی‌خواند و mask فرآیندی که آن را آغاز کرده است، به ارث می‌برد. یک cron job، یک git hook و برنامه‌ای که توسط یک service manager اجرا می‌شود، همگی در این دسته آخر قرار می‌گیرند؛ بنابراین mask آن‌ها همان چیزی است که فرآیند والدشان داشته است.

به همین دلیل است که گزارش «من آن را در /etc/profile تنظیم کردم اما سرویس همچنان با mode اشتباه می‌نویسد» بسیار رایج است. سرویس هرگز آن فایل را نخوانده است.

محل تنظیم برای ماندگاری تغییرات

مقدار mask را در جایی تنظیم کنید که workload واقعاً شروع می‌شود، زیرا هر مسیر شروع، فایل متفاوتی را می‌خواند.

  1. برای حساب‌هایی که وارد سیستم می‌شوند: UMASK در /etc/login.defs، که توسط pam_umask برای هر نشست (session) در ماشین اعمال می‌شود. این تنظیم در سطح کل ماشین است، بنابراین تمام حساب‌ها را هم‌زمان تغییر می‌دهد.
  2. برای یک حساب کاربری خاص: آرگومان umask= در خط pam_umask.so نیز در سطح کل ماشین است، بنابراین مقدار مختص به هر کاربر باید در فیلد GECOS آن کاربر قرار گیرد، یا برای login shellها در ~/.profile و برای shellهای تعاملی در ~/.bashrc تنظیم شود.
  3. برای یک daemon تحت systemd: مقدار UMask= در بخش [Service] از فایل unit. یک unit توسط service manager شروع می‌شود، بنابراین /etc/profile هرگز خوانده نمی‌شود و pam_umask هرگز اجرا نمی‌گردد. فایل unit تنها موردی است که به daemon می‌رسد.
  4. برای اسکریپتی که توسط cron یا یک hook شروع می‌شود: یک دستور umask صریح در خط اول، پیش از آنکه اسکریپت فایلی ایجاد کند.
[Service]
UMask=<the octal mask you chose>

سپس از یک شروع تازه در همان مسیر، و نه از shellای که فایل را در آن ویرایش کرده‌اید، صحت تنظیمات را بررسی کنید. shell فعلی شما در حال حاضر mask خود را در حافظه دارد و ویرایش یک فایل پیکربندی، تأثیری بر پردازش‌های در حال اجرا نمی‌گذارد.

bash -lc 'umask'
sudo -i umask

چرا اجرای chmod پس از ایجاد فایل، راه‌حل یکسانی نیست

chmod فایل‌های موجود را اصلاح می‌کند. اما mask، حالت (mode) فایل‌هایی را تعیین می‌کند که هنوز ایجاد نشده‌اند. اگر chmod -R را روی یک دایرکتوری اجرا کنید، فایل بعدی که توسط سرویس نوشته می‌شود دوباره با همان حالت قدیمی ایجاد خواهد شد؛ زیرا آن حالت از سوی پردازشِ ایجادکننده تعیین می‌شود و هیچ‌چیز در دایرکتوری آن را تغییر نمی‌دهد.

علاوه بر این، یک بازهٔ زمانی آسیب‌پذیر وجود دارد. از لحظهٔ ایجاد فایل تا لحظه‌ای که chmod اجرا می‌شود، فایل با دسترسی بازتر روی دیسک قرار دارد و هر پردازشی که به دایرکتوری دسترسی خواندن داشته باشد، می‌تواند آن را باز کند. برای یک کلید خصوصی یا آرشیو پشتیبان، این بازهٔ زمانی همان ریسکی است که قصد حذف آن را داشتید.

در عوض، حالت فایل را در لحظهٔ ایجاد تعیین کنید. install -m u=rw,go= newfile /etc/app/newfile فایل مقصد را با یک حالت صریح می‌نویسد و mkdir -m همین کار را برای دایرکتوری انجام می‌دهد. هر دو دستوری که نام می‌برید را می‌پذیرند و mask را نادیده می‌گیرند. ssh-keygen حالت فایل را روی کلید خصوصی که می‌نویسد تنظیم می‌کند؛ به همین دلیل است که آن فایل خاص اغلب روی سیستمی که بقیه فایل‌هایش مشکل دارند، درست عمل می‌کند.

قربانی معمول در این شرایط SSH است. یک ~/.ssh که با یک mkdir ساده ساخته شده، یا یک authorized_keys که با cat >> به انتهای فایل اضافه شده، از mask شل شما تبعیت می‌کند. با فعال بودن StrictModes، سرویس sshd از خواندن فایل کلید از دایرکتوری که برای گروه قابل‌نوشتن است، خودداری می‌کند. به کلاینت پیام Permission denied (publickey) نمایش داده می‌شود، در حالی که /var/log/auth.log سرور علت واقعی را ثبت می‌کند:

Authentication refused: bad ownership or modes for directory /home/deploy/.ssh

این بررسی عامدانه است و ایمن‌سازی SSH روی یک VPS به پایداری آن وابسته است. پیش از ایجاد حساب‌هایی که از آن استفاده خواهند کرد، mask را روی سرور جدید اندازه‌گیری کنید؛ این کار را در کنار ده دقیقه اول روی یک VPS جدید انجام دهید تا حالت تمام فایل‌هایی که آن حساب‌ها می‌نویسند، از پیش تعیین‌شده باشد.

کپی‌ها و آرشیوها از mask صرف‌نظر می‌کنند

cp -p و rsync -a همان مدی (mode) ثبت‌شده روی فایل مبدأ را بازیابی می‌کنند، بنابراین mask هیچ تأثیری بر نتیجه ندارد. tar نیز هنگام استخراج فایل‌ها به عنوان root یا به عنوان یک کاربر عادی با استفاده از -p، همین رفتار را دارد. فایلی که از یک نسخه پشتیبان بازیابی می‌شود، مدی زمان ایجاد نسخه پشتیبان را حفظ می‌کند. پیش از آنکه نتیجه بگیرید یک mask صحیح نادیده گرفته شده است، این موضوع را بررسی کنید: برای داده‌های بازیابی‌شده، mask هرگز مورد استعلام قرار نگرفته است.

FAQ

چرا cron job من فایل‌ها را با دسترسی متفاوتی نسبت به نشست ssh ایجاد می‌کند؟

یک cron job یک نشست ورود (login session) نیست، بنابراین /etc/profile یا ~/.profile خوانده نمی‌شوند و pam_umask برای آن اجرا نمی‌شود. این job، مقدار umask فرآیندی که آن را آغاز کرده است به ارث می‌برد. یک دستور umask صریح در خط اول اسکریپت خود قرار دهید، پیش از آنکه هر فایلی ایجاد شود. همچنین مقدار umask را یک‌بار از داخل خود job چاپ کنید تا به جای مشاهده مقدار shell خودتان، مقدار واقعی آن job را ببینید.

چرا فایل /etc/login.defs یک مقدار را نشان می‌دهد اما shell من مقدار دیگری را چاپ می‌کند؟

مقدار UMASK در /etc/login.defs تنها آخرین گزینه برای pam_umask است. این ماژول ابتدا اولویت را به ورودی umask= در فیلد GECOS کاربر می‌دهد، سپس به آرگومان umask= در خط pam_umask.so در فایل /etc/pam.d/ نگاه می‌کند. رفتار usergroups که با USERGROUPS_ENAB فعال می‌شود، رقم مربوط به گروه را برای هر حساب غیر root که نام گروه اصلی آن با نام کاربری‌اش یکی است، بازنویسی می‌کند. دستورات grep -rn pam_umask /etc/pam.d/ و id -un; id -gn را اجرا کنید تا ببینید کدام‌یک از این موارد برای حساب شما اعمال می‌شود.

آیا umask می‌تواند یک فایل را قابل‌اجرا (executable) کند؟

خیر. ماسک فقط می‌تواند بیت‌هایی را از آنچه برنامه ایجادکننده درخواست کرده است، حذف کند. دستور touch هرگز درخواست بیت اجرایی نمی‌کند، بنابراین هیچ ماسکی نمی‌تواند فایل اجرایی تولید کند. این موضوع را در یک دایرکتوری موقت با ( umask a=rwx; touch f; stat -c '%a %A' f ) بررسی کنید. برای داشتن بیت اجرایی، به دستور chmod یا برنامه‌ای مانند install -m نیاز دارید که در زمان ایجاد فایل، درخواست بیت اجرایی کند.

umask را برای یک سرویس systemd کجا تنظیم کنم؟

در فایل unit، با استفاده از UMask= در بخش [Service]. یک سرویس توسط مدیر سرویس (service manager) شروع می‌شود و نه توسط یک نشست ورود، بنابراین فایل‌های راه‌اندازی shell خوانده نمی‌شوند و pam_umask برای آن اجرا نمی‌شود. پس از systemctl daemon-reload و راه‌اندازی مجدد سرویس، از بیرون بررسی کنید: اجازه دهید سرویس یک فایل ایجاد کند، سپس نتیجه را با stat -c '%a %n' مشاهده کنید.

آیا ماسک پیش‌فرض با دسترسی نوشتن برای گروه (group-writable) امن است؟

این وضعیت تا زمانی که گروه دقیقاً یک عضو داشته باشد امن است؛ این همان فرضی است که در طرح «گروه خصوصی کاربر» (user private group) وجود دارد. اگر حساب دومی به آن گروه اضافه کنید، تمام فایل‌هایی که حساب اول ایجاد کرده است بلافاصله برای عضو جدید قابل‌نوشتن می‌شوند، بدون اینکه نیاز باشد دستوری روی آن فایل‌ها اجرا شود. دستورات id -un و id -gn را اجرا کنید: اگر نام چاپ شده یکسان باشد، یعنی شما در یک گروه خصوصی هستید. اگر گروهی را بین چندین حساب به اشتراک می‌گذارید، ماسکی تنظیم کنید که بیت نوشتن گروه را حذف کند، سپس یک فایل ایجاد کرده و stat -c '%a %n' را بررسی کنید تا مطمئن شوید تغییر اعمال شده است.

#umask#permissions#pam#login-defs#linux