Så hittar du vilken systemd-sandbox som nekade din OpenVPN-hook
Hooken fungerar som root och misslyckas under systemd eftersom uniten behåller färre kapabiliteter, en mindre /dev och skrivskyddade sökvägar.
Hooken fungerar när du kör den för hand som root och returnerar Operation not permitted i samma stund som tjänsten startar den. Filen ändrades inte mellan de körningarna; miljön gjorde det. systemd ger processen en mindre värld: färre kapabiliteter, en annan /dev, skrivskyddade sökvägar i filsystemet och ett tak för antalet uppgifter. Läs felet som en gräns som nekade en specifik operation, inte som ett skript som gick sönder.
Börja med operationen som nekades
En logg rad som säger att uniten misslyckades säger dig nästan ingenting. Den användbara raden namnger anropet som nekades: en enhetsnod som inte kunde öppnas, en katalog som inte kunde skrivas till, ett namnutrymme som inte kunde skapas. Hitta det anropet innan du rör en enda direktiv, eftersom var och en motsvarar en annan begränsning. Läs sedan uniten som faktiskt kördes, inklusive drop-ins, i stället för filen som paketet levererade.
Frågan nedan skriver ut fragmentets sökväg, drop-in-katalogerna och de egenskaper som spelar roll för ett nekande av en enhet eller ett filsystem. Läs alla innan du avgör vilken gräns som är relevant:
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'Inget i det blocket skriver. Om det nekade anropet saknas i journalen, höj klientens detaljnivå för en körning i stället för att gissa direktiv. En körande process bevisar att uniten startade, inte att hooken kördes med de privilegier den behövde.
Ett namnutrymme behöver mount-rättigheter som en TUN-enhet aldrig använder
Två operationer som ser likadana ut inuti en hook har olika behörighetsprofiler. Att öppna /dev/net/tun och skapa gränssnittet kräver nätverksadministration plus åtkomst till den noden. Att skapa ett namngivet nätverksnamnutrymme kräver också mount-relaterat arbete, eftersom namnutrymmesverktyget monterar en färsk /sys vy och binder en tillståndskatalog på plats. Den uppströms klientenheten lämnar medvetet mount-administrationen utanför sin begränsningsmängd, så namnutrymmessteget nekas medan själva tunneln skulle ha fungerat.
Det är därför som att bevilja enhetsnoden inte räddar skriptet. Ett enhetstillstånd besvarar en fråga, om den här processen får öppna den här noden, och namnutrymmesfrågan är en annan. Matcha det misslyckade anropet mot dess kapabilitet innan du vidgar något.
Ett test i ditt skal ärver varje kapabilitet som en obegränsad root har, och uniten ärver det som dess begränsningsmängd behöll. De två körningarna är olika experiment även när binären är identisk.
Enheter, skyddade sökvägar och uppgiftstak
När den nekade operationen är känd förklarar tre grupper av direktiv de flesta fel. Enhetsåtkomst styrs av PrivateDevices, som ersätter /dev med en minimal uppsättning pseudoenheter, och av DevicePolicy med DeviceAllow, som avgör vad cgroupen tillåter. /dev/net/tun är inte en del av den minimala uppsättningen, så en unit med PrivateDevices=yes kan inte öppna den vad enhetspolicyn än säger.
Filsystemsskydd besvarar en andra fråga. ProtectHome=yes gör /home, /root och /run/user otillgängliga, och ProtectSystem=full gör /usr, /boot och /etc skrivskyddade. En hook som skriver bredvid sin egen konfigurationsfil fungerar därför i ditt skal och misslyckas under uniten, med samma behörighetsform som en saknad kapabilitet. Flytta den skrivbara indatan till en sökväg som policyn tillåter, eller lägg till en smal ReadWritePaths= post.
Resursgränser är den tredje gruppen, och den minst uppenbara. TasksMax= begränsar hur många uppgifter uniten får skapa, och ett tak för filbeskrivningar beter sig på samma sätt för sockets och handtag. En driftsättning som växte förbi taket misslyckas med ett resursfel i stället för ett behörighetsfel, vilket är anledningen till att det exakta meddelandet fortfarande spelar roll.
Flytta namnutrymmessteget ut ur hooken
Förberedelse som kräver en behörighet som klienten inte får ha hör hemma i sin egen unit. En oneshot som körs före klienten, behåller sitt resultat och är ordnad före anslutningen ger namnutrymmessteget vad det behöver utan att lämna dessa rättigheter till tunnelprocessen. Klienten refererar sedan till den uniten i stället för att anropa skriptet från sin up-hook.
Mallen nedan är en startform, inte något som appliceras oläst. Den behåller förberedelsen där behörigheten bor och lämnar tunneluniten orörd:
# /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.targetLägg till ordningen med en drop-in i stället för att redigera den paketerade uniten, så att en uppgradering inte tyst kan tappa bort din ändring. Katalogen är knuten till instansnamnet, så en andra profil behöver sin egen fil:
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-reloadUpprepa samma anrop efter ändringen
En reparation bekräftas först när samma anrop ger ett annat resultat. Starta om båda uniterna, kör operationen som misslyckades och jämför dess utfall med den journalrad du sparade före ändringen. En unit som startar medan hooken aldrig körs igen är ingen reparation.
Sekvensen nedan håller jämförelsen ärlig, eftersom den kontrollerar det nekade anropet och inte unitens färg. Spara dess utdata bredvid journalraden du fångade före ändringen:
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 tun0Räkna nekandena i stället för att läsa den sista raden, eftersom en hook kan misslyckas, försöka igen och lyckas, och bara antalet visar om gränsen flyttades. Lämna en kommentar i drop-inen som namnger direktivet du lossade och orsaken, eftersom nästa uppgradering jämför din konfiguration med den paketerade standarden. En unit som bara startar när varje skydd är avstängt är inte åtgärdad.