როგორ გავარკვიოთ, რომელი systemd sandbox-ი უარყო თქვენი OpenVPN hook-ი
hook-ი root-ის სახით მუშაობს და systemd-ის ქვეშ მარცხდება, რადგან unit-ი ინახავს ნაკლებ შესაძლებლობას, პატარა /dev-ს და მხოლოდ-წაკითხვად ბილიკებს.
hook-ი მუშაობს, როცა ხელით, 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-ი დაიწყო, და არა რომ hook-ი საჭირო უფლებებით მუშაობდა.
სახელთა სივრცეს სჭირდება mount უფლებები, რომლებსაც TUN მოწყობილობა არასდროს იყენებს
ერთი hook-ის შიგნით ერთნაირად მოჩვენებულ ორ ოპერაციას განსხვავებული ნებართვის პროფილი აქვს. /dev/net/tun გახსნა და ინტერფეისის შექმნა მოითხოვს ქსელის ადმინისტრირებას და ამ კვანძზე წვდომას. დასახელებული ქსელური სახელთა სივრცის შექმნა ასევე მოითხოვს mount სამუშაოს, რადგან სახელთა სივრცის ინსტრუმენტი mount-ს უკეთებს ახალ /sys ხედს და აბამს მდგომარეობის საქაღალდეს თავის ადგილზე. ზედა დინების კლიენტის unit-ი განზრახ ტოვებს mount ადმინისტრირებას თავისი საზღვრების სიმრავლის გარეთ, ამიტომ სახელთა სივრცის ნაბიჯი უარყოფილია, მაშინ როცა თვით გვირაბი იმუშავებდა.
ამიტომ მოწყობილობის კვანძის მიცემა სკრიპტს არ იხსნის. მოწყობილობის ნებართვა პასუხობს ერთ კითხვას, შეიძლება თუ არა ამ პროცესმა ეს კვანძი გახსნას, ხოლო სახელთა სივრცის კითხვა სხვაა. შეუსაბამეთ ჩავარდნილი გამოძახება მის შესაძლებლობას, სანამ რამეს გააფართოებთ.
თქვენს shell-ში ტესტი მემკვიდრეობით იღებს ყველა შესაძლებლობას, რაც შეუზღუდავ root-ს აქვს, ხოლო unit-ი იღებს იმას, რაც მისმა საზღვრების სიმრავლემ შემოინახა. ეს ორი გაშვება განსხვავებული ექსპერიმენტია მაშინაც კი, როცა ბინარი იდენტურია.
მოწყობილობები, დაცული ბილიკები და დავალებების ზედა ზღვრები
როცა უარყოფილი ოპერაცია ცნობილია, ინსტრუქციების სამი ჯგუფი ხსნის ჩავარდნების უმეტესობას. მოწყობილობებზე წვდომას არეგულირებს PrivateDevices, რომელიც ცვლის /dev -ს მინიმალური ფსევდო-მოწყობილობების სიმრავლით, და DevicePolicy -თან ერთად DeviceAllow, რომლებიც წყვეტენ, რას უშვებს cgroup. /dev/net/tun ამ მინიმალური სიმრავლის ნაწილი არ არის, ამიტომ PrivateDevices=yes მქონე unit-ი ვერ გახსნის მას, რაც არ უნდა თქვას მოწყობილობის პოლიტიკამ.
ფაილური სისტემის დაცვა პასუხობს მეორე კითხვას. ProtectHome=yes ხდის /home, /root და /run/user მიუწვდომელს, და ProtectSystem=full ხდის /usr, /boot და /etc მხოლოდ-წაკითხვადს. ამიტომ hook-ი, რომელიც წერს საკუთარი კონფიგურაციის ფაილის გვერდით, მუშაობს თქვენს shell-ში და მარცხდება unit-ის ქვეშ, იმავე ნებართვის ფორმით, რაც აკლია შესაძლებლობას. გადაიტანეთ ჩასაწერი შეყვანა პოლიტიკის მიერ დაშვებულ ბილიკზე, ან დაამატეთ ვიწრო ReadWritePaths= ჩანაწერი.
რესურსების ლიმიტები მესამე ჯგუფია და ყველაზე ნაკლებად თვალსაჩინო. TasksMax= ზღუდავს, რამდენი დავალების შექმნა შეუძლია unit-ს, ხოლო ფაილის დესკრიპტორების ზედა ზღვარი იგივენაირად იქცევა socket-ებისა და სახელურების მიმართ. განლაგება, რომელიც ზღვარს გასცდა, მარცხდება რესურსის შეცდომით და არა ნებართვის შეცდომით, და სწორედ ამიტომ ზუსტ შეტყობინებას კვლავ აქვს მნიშვნელობა.
გამოიტანეთ სახელთა სივრცის ნაბიჯი hook-იდან
მომზადება, რომელსაც სჭირდება უფლება, რომელიც კლიენტს არ უნდა ჰქონდეს, საკუთარ unit-ს ეკუთვნის. oneshot-ი, რომელიც კლიენტამდე მუშაობს, ინახავს თავის შედეგს და დალაგებულია კავშირამდე, აძლევს სახელთა სივრცის ნაბიჯს საჭიროს, იმ უფლებების გვირაბის პროცესისთვის გადაცემის გარეშე. შემდეგ კლიენტი მიუთითებს ამ unit-ზე და არა იძახებს სკრიპტს თავისი up hook-იდან.
ქვემოთ მოცემული შაბლონი საწყისი ფორმაა და არა ის, რაც წაუკითხავად გამოიყენება. ის ინახავს მომზადებას იქ, სადაც უფლება ცხოვრობს, და გვირაბის 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-ი, რომელიც ირთვება, მაშინ როცა hook-ი აღარასდროს მუშაობს, შეკეთება არ არის.
ქვემოთ მოცემული თანმიმდევრობა შედარებას პატიოსნად ინახავს, რადგან ამოწმებს უარყოფილ გამოძახებას და არა 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დათვალეთ უარყოფები და ნუ წაიკითხავთ ბოლო სტრიქონს, რადგან hook-ი შეიძლება ჩავარდეს, ხელახლა სცადოს და წარმატდეს, და მხოლოდ რაოდენობა გვიჩვენებს, გადაინაცვლა თუ არა საზღვარმა. დატოვეთ კომენტარი drop-in-ში, სადაც დაასახელებთ შესუსტებულ ინსტრუქციას და მიზეზს, ვინაიდან შემდეგი განახლება თქვენს კონფიგურაციას პაკეტის ნაგულისხმევს ადარებს. unit-ი, რომელიც მხოლოდ მას შემდეგ ირთვება, რაც ყველა დაცვა გამორთულია, არ არის გამოსწორებული.