اجرای یک اقدام پس از اتصال VPN بهعنوان کاربر، نه بهعنوان دیمن
متغیر محیطی USER=alice تغییر نمیدهد که یک قلاب با کدام حساب اجرا میشود. رویداد، محرک و اقدام را جدا کنید و سپس UID را اثبات کنید.
قلابهای اتصال روی دیمن VPN با همان دیمن اجرا میشوند و معمولاً این یعنی با root اجرا میشوند. همان لحظهای که یکی از این قلابها در پوشه خانگی کسی مینویسد یا یک کمککننده میزکار را دوباره راه میاندازد، شما به یک رویداد شبکه توانایی اقدام بهعنوان هر کاربر محلی دلخواه را دادهاید. میانبر معمول، یعنی خواندن نام کاربری از یک متغیر محیطی و پاس دادن آن، این را درست نمیکند. فقط اقدام را دوباره برچسب میزند بدون آنکه حساب اجراکننده را تغییر دهد. آنچه کار میکند یک تحویل سهبخشی است که در آن گام دارای امتیاز میگوید چه رخ داده و یک واحد جداگانه اقدام را با هویت درست انجام میدهد.
نام کاربری در محیط یک کلید هویت نیست
یونیکس تعیین میکند که یک فرایند به چه چیزی دست بزند بر اساس شناسه کاربری مؤثرش، نه بر اساس رشتههای محیطش. تنظیم USER=alice یا صادر کردن LOGNAME تغییر میدهد که نرمافزار بیدقت در خروجیاش چه چاپ کند؛ در بررسی مالکیت فایلها هیچ چیزی را تغییر نمیدهد. قلابی که با root و با آن متغیر تنظیمشده اجرا میشود همچنان فایلهای آلیس را با root باز میکند.
نامها هم یک نام نیستند. ورود دسترسی از راه دور، حساب محلی یونیکس و نام مشترک گواهی سه شناسهاند که غالباً یکی میشوند و گاهی نه. کاربری که بهعنوان a.smith احراز هویت میکند ممکن است هیچ حساب محلی نداشته باشد یا از طریق سرویس دایرکتوری به asmith نگاشت شود. هر طراحیای که فرض کند یک رشته هر سه نقش را انجام میدهد در اولین پیمانکار میشکند.
پس پرسش اول این نیست که «کدام کاربر متصل است» بلکه «کدام حساب محلی اجازه انجام این اقدام را دارد». به آن از پایگاه داده حسابهای خودتان پاسخ دهید، نه از آنچه کلاینت فرستاده است:
id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json
دستور سوم همانی است که مردم از آن میگذرند. اگر فایل هدف مالکش root باشد چون قلاب قبلی آن را ساخته، یک اقدام کاربری درست پیکربندیشده با خطای مجوز شکست میخورد که شبیه مشکل systemd به نظر میرسد و نیست.
دیمن همانجا که هست میماند
تغییر User= خود سرویس VPN به آلیس اهرم اشتباهی است. نحوه اتصال واسطها، خواندن کلیدها، نوشتن فایل وضعیت و مدیریت مسیرها توسط دیمن را تغییر میدهد و همه این ریسک را به واحدی منتقل میکند که کار میکرد. دیمن به امتیازهایش نیاز دارد؛ اقدام بعدی ندارد.
پیش از دست زدن به واحد ببینید واقعاً چه میکند. دو دستور هویت اجرا و خطوط فرمان دقیق در حال اجرا را به شما میگویند، از جمله هر چیزی که یک بازنویسی افزوده است:
systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server
در نصب پیشفرض بسته، دیمن root است و اسکریپت --up آن را به ارث میبرد. آن را تأیید کنید نه فرض، چون توزیعی سختشده ممکن است آن را به حسابی اختصاصی منتقل کرده باشد، و یک drop-in دستنویس ممکن است قلاب را از طریق پوششی با قواعد متفاوت اجرا کند.
سه بخش نامدار، یک محرک باریک
ترتیبی که از نگهداری جان سالم به در میبرد یک رویداد، یک محرک و یک اقدام دارد. دیمن گزارش میدهد که یک نشست نامدار بالا آمده است. محرک این واقعیت کوچک را میپذیرد و یک اقدام ثابت میخواهد. اقدام زیر حساب محلیای اجرا میشود که مالک فایلهای لمسشده است. هیچ چیز در زنجیره مسیر فرمان دلخواه را نمیپذیرد.
خود اقدام یک واحد سرویس معمولی است و هویت یک دستورالعمل درون آن است:
[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i
توجه کنید که User=alice برای کل واحد اعمال میشود، و systemd یک استثنا را مستند میکند که دانستنش ارزش دارد: فرمانی با پیشوند + بدون توجه به آن با امتیاز کامل اجرا میشود. این پیشوند ابزاری مشروع برای یک گام دارای امتیاز و راهی راحت برای خنثی کردن جداسازیای است که همین حالا ساختهاید، پس هر + در آن واحد را چیزی بدانید که باید در بازبینی توجیه شود.
ورودی محرک را کمینه و غیرمحرمانه نگه دارید. شناسه نشست، حساب نگاشتشده و یک برچسب زمانی معمولاً همه چیز لازم است. هرگز یک تکه پوسته، مسیر یک فایل اجرایی یا بلوک محیطی ارائهشده از سوی کلاینت را از مرز عبور ندهید، چون سمت گیرنده راهی برای تشخیص مقدار مشروع از مقدار تزریقشده ندارد.
هویتی که واقعاً اجرا شد را بررسی کنید
بازبینی پیکربندی شاهد نیست. پس از یک اتصال آزمایشی تأییدشده، UIDی را که به فایل دست زده و UIDی را که دیمن همچنان استفاده میکند تأیید کنید:
stat -c '%U %G %y %n' /home/alice/.local/state/vpn-status.json
ps -o user=,pid=,cmd= -p "$(pgrep -o -f 'openvpn --daemon')"
systemd-run --uid=alice --pipe /usr/local/bin/refresh-vpn-status --dry-run
خط اول باید آلیس و یک برچسب زمانی درون پنجره آزمایش شما را نشان دهد. خط دوم باید همچنان root یا حساب سرویس بسته را نشان دهد که اثبات میکند دیمن جابهجا نشده است. خط سوم ارزانترین راه آزمودن مجوزهای اقدام بدون انتظار برای اتصال دوباره واقعی است.
اگر مالک فایل آلیس است اما گروه یا حالت آن بین اجراها تغییر کرده، بهدنبال UMask= یا تفاوت مجوز پوشه بگردید نه خطای منطقی. اگر برچسب زمانی اصلاً تکان نخورده، محرک هرگز فعال نشده و گام بعدی سودمند journalctl -u برای واحد اقدام است نه یک اتصال دوباره دیگر کلاینت.
آنچه یک رویداد اتصال همچنان اثبات نمیکند
تونل برقرارشده یک واقعیت است. معنایش این نیست که نشست میزکار باز شده، گذرگاه نشست در دسترس است یا منبع خصوصیای که اقدام میخواهد اکنون موجود است. یک واحد oneshot زیر UID آلیس هویت او را دارد و هیچ چیز از نشستش را نه، پس هر چیزی که گذرگاه گرافیکی میخواهد باید از سازوکار پشتیبانیشده نشست کاربر عبور کند و روشن کند وقتی او خارج شده چه میشود.
اتصالهای دوباره هم تکرار میشوند. لپتاپی که در قطار میخوابد میتواند در یک ساعت یک دوجین رویداد اتصال تولید کند، و قلابی که هر بار یک خط میافزاید فایل وضعیت را به گزارش رویدادی تبدیل میکند که هیچکس نمیتواند بخواند. اقدام را خودتوان کنید یا بگذارید یک رکورد کلیدخورده با نشست را جایگزین کند.
در پایان، شعاع آسیب را در خود واحد محدود کنید: نمایی فقطخواندنی از فایلسیستم جدا از پوشه وضعیت، بدون امتیاز جدید و با یک مهلت صریح. جداسازی دیمن و اقدام کاربر به خودی خود ارزش دارد، اما وقتی اقدام جز همان یک چیزی که تأیید کردید کاری نکند بسیار بیشتر ارزش دارد.