هوک وقتی آن را دستی به‌صورت root اجرا می‌کنید کار می‌کند و برمی‌گرداند Operation not permitted را درست همان لحظه‌ای که سرویس آن را راه می‌اندازد. فایل بین این اجراها تغییر نکرد؛ محیط تغییر کرد. systemd دنیای کوچک‌تری به فرایند می‌دهد: توانایی‌های کمتر، یک /devمتفاوت، مسیرهای فایل‌سیستم فقط‌خواندنی و سقفی برای وظایف. شکست را مرزی بخوانید که یک عملیات مشخص را رد کرده است، نه اسکریپتی که خراب شده.

از همان عملیاتی شروع کنید که رد شد

خطی از لاگ که می‌گوید unit شکست خورد تقریباً هیچ چیز به شما نمی‌گوید. خط مفید، فراخوانی‌ای را نام می‌برد که رد شده است: گره دستگاهی که باز نشد، پوشه‌ای که در آن نوشته نشد، فضای نامی که ساخته نشد. پیش از دست زدن به حتی یک دایرکتیو، همان فراخوانی را پیدا کنید، چون هرکدام به محدودیت متفاوتی نگاشت می‌شوند. سپس unitی را بخوانید که واقعاً اجرا شد، همراه با drop-inها، نه فایلی که بسته تحویل داده است.

پرس‌وجوی زیر مسیر قطعه، پوشه‌های drop-in و ویژگی‌هایی را چاپ می‌کند که برای رد شدن یک دستگاه یا فایل‌سیستم مهم‌اند. پیش از تصمیم درباره اینکه کدام مرز مربوط است، همه را بخوانید:

systemctl show "[email protected]" -p FragmentPath -p DropInPaths -p PrivateDevices -p DevicePolicy -p DeviceAllow -p ProtectHome -p ProtectSystem -p TasksMax
journalctl -u "[email protected]" -b --no-pager | grep -iE 'operation not permitted|permission denied|cannot open|failed'

هیچ چیز در آن بلوک نمی‌نویسد. اگر فراخوانی ردشده در ژورنال نیست، به‌جای حدس زدن دایرکتیوها، برای یک اجرا میزان توضیح‌پذیری کلاینت را بالا ببرید. فرایندی که در حال اجراست ثابت می‌کند unit شروع شده، نه اینکه هوک با اختیارات لازم اجرا شده است.

فضای نام به اختیارات mount نیاز دارد که دستگاه TUN هرگز از آن استفاده نمی‌کند

دو عملیات که درون یک هوک شبیه هم به نظر می‌رسند، پروفایل‌های مجوز متفاوتی دارند. باز کردن /dev/net/tun و ساختن رابط به مدیریت شبکه به‌علاوه دسترسی به آن گره نیاز دارد. ساختن یک فضای نام شبکه‌ای نام‌دار هم به کار mount نیاز دارد، چون ابزار فضای نام یک /sys نمای تازه mount می‌کند و یک پوشه وضعیت را سر جایش bind می‌کند. unit کلاینت بالادستی عمداً مدیریت mount را بیرون از مجموعه مرزهایش نگه می‌دارد، بنابراین گام فضای نام رد می‌شود، در حالی که خود تونل کار می‌کرد.

به همین دلیل دادن گره دستگاه اسکریپت را نجات نمی‌دهد. مجوز دستگاه به یک پرسش پاسخ می‌دهد، اینکه آیا این فرایند می‌تواند این گره را باز کند، و پرسش فضای نام پرسش دیگری است. پیش از هر گسترشی، فراخوانی شکست‌خورده را با توانایی‌اش تطبیق دهید.

یک آزمایش در شل شما هر توانایی‌ای را به ارث می‌برد که root نامحدود دارد، و unit آنچه را مجموعه مرزهایش نگه داشته به ارث می‌برد. این دو اجرا آزمایش‌های متفاوتی هستند حتی وقتی باینری یکسان است.

دستگاه‌ها، مسیرهای محافظت‌شده و سقف وظایف

وقتی عملیات ردشده شناخته شد، سه گروه دایرکتیو بیشتر شکست‌ها را توضیح می‌دهند. دسترسی به دستگاه‌ها را PrivateDevicesتنظیم می‌کند، که /dev را با مجموعه‌ای کمینه از شبه‌دستگاه‌ها جایگزین می‌کند، و DevicePolicy با DeviceAllow، که تعیین می‌کنند cgroup چه چیزی را مجاز می‌داند. /dev/net/tun بخشی از آن مجموعه کمینه نیست، پس unitی که PrivateDevices=yes دارد نمی‌تواند آن را باز کند، هر چه سیاست دستگاه بگوید.

محافظت فایل‌سیستم به پرسش دومی پاسخ می‌دهد. ProtectHome=yes باعث می‌شود /home, /root و /run/user غیرقابل‌دسترس شوند، و ProtectSystem=full باعث می‌شود /usr, /boot و /etc فقط‌خواندنی شوند. بنابراین هوکی که کنار فایل پیکربندی خودش می‌نویسد در شل شما کار می‌کند و زیر unit شکست می‌خورد، با همان شکل مجوزی که یک توانایی غایب دارد. ورودی نوشتنی را به مسیری ببرید که سیاست اجازه می‌دهد، یا یک ورودی ReadWritePaths= باریک اضافه کنید.

محدودیت‌های منابع گروه سوم هستند، و کم‌واضح‌ترین. TasksMax= سقف می‌زند بر اینکه unit چند وظیفه می‌تواند بسازد، و سقف توصیفگر فایل برای سوکت‌ها و دستگیره‌ها هم همین‌طور رفتار می‌کند. استقراری که از سقف گذشته باشد با خطای منابع شکست می‌خورد نه خطای مجوز، و به همین دلیل پیام دقیق هنوز مهم است.

گام فضای نام را از هوک بیرون ببرید

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

قالب زیر یک شکل آغازین است، نه چیزی که ناخوانده اعمال شود. آماده‌سازی را همان‌جا نگه می‌دارد که اختیار زندگی می‌کند و unit تونل را دست‌نخورده می‌گذارد:

# /etc/systemd/system/[email protected]
[Unit]
Description=Namespace preparation for %i
Before=openvpn-client@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/netns-up.sh %i
CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_ADMIN
[Install]
WantedBy=multi-user.target

ترتیب را با یک drop-in اضافه کنید نه با ویرایش unit بسته‌بندی‌شده، تا یک ارتقا نتواند بی‌صدا تغییر شما را بیندازد. پوشه به نام نمونه گره خورده است، پس پروفایل دوم فایل خودش را لازم دارد:

sudo mkdir -p /etc/systemd/system/[email protected]
sudo tee /etc/systemd/system/[email protected]/10-namespace.conf >/dev/null <<'CONF'
[Unit]
[email protected]
[email protected]
CONF
sudo systemctl daemon-reload

همان فراخوانی را پس از تغییر تکرار کنید

ترمیم تنها وقتی تأیید می‌شود که همان فراخوانی نتیجه متفاوتی بدهد. هر دو unit را دوباره راه بیندازید، عملیاتی را که شکست خورد اجرا کنید و نتیجه‌اش را با خط ژورنالی که پیش از تغییر ذخیره کرده‌اید مقایسه کنید. unitی که راه می‌افتد در حالی که هوک دیگر هرگز اجرا نمی‌شود، ترمیم نیست.

دنباله زیر مقایسه را صادق نگه می‌دارد، چون فراخوانی ردشده را بررسی می‌کند نه رنگ unit را. خروجی‌اش را کنار خط ژورنالی که پیش از تغییر گرفته‌اید ذخیره کنید:

sudo systemctl restart "[email protected]" "[email protected]"
systemctl status "[email protected]" --no-pager
journalctl -u "[email protected]" -b --no-pager | grep -c 'Operation not permitted'
ip -brief addr show tun0

ردشدن‌ها را بشمارید به‌جای خواندن آخرین خط، چون هوک می‌تواند شکست بخورد، دوباره تلاش کند و موفق شود، و تنها شمارش نشان می‌دهد که آیا مرز جابه‌جا شده است. در drop-in یادداشتی بگذارید که دایرکتیوی را که شل کرده‌اید و دلیلش را نام ببرد، چون ارتقای بعدی پیکربندی شما را با پیش‌فرض بسته مقایسه می‌کند. unitی که فقط پس از غیرفعال شدن هر محافظتی راه می‌افتد ترمیم نشده است.

مقالات مرتبط