Bash függvények sudo-val

Testépítű pingvin képe a pixabay.com-ról

Ha Linuxon szeretnél adminisztrátori jogosultsággal futtatni utasításokat, gyakran egy nem adminisztrátor felhasználóval lépsz be, majd a sudo paranccsal szerzel az adott utasítás futtatásának idejére adminisztrátori jogosultságot, avagy root jogot. Ugyancsak gyakori, hogy egy program elég nagyra nő, hogy a sok-sok shell script mellett, azokban függvényeket is írsz. A nagy kérdés, hogy mi van, ha ezek közül az egyik függvénynek adminisztrátori jogokkal kell működnie, de az azt meghívó szkriptnek nem. Egy részt, talán itt az ideje újratervezni, de talán megvan az okod rá és kioktatás helyett okításra van szükséged, ezért a következőkben megosztok veled pár lehetőséget. Az alábbiak megértéséhez érdemes elolvasni a Bash argumentumok és titkaik című cikket is.

Tartalomjegyzék

Használj függvény helyett új szkriptet

[Tartalom]

Bash-ben függvény és szkript futtatása között nem sok különbség van. Talán pazarlónak tűnik például két sor miatt egy külön fájlt létrehozni és a futtatási jogokoat kezelni, de míg egy fájlra beállíthatsz a visudo paranccsal a /etc/sudoers fájlban külön is engedélyt sudo használatára egy felhasználó részére, függvényre nem. Ráadásul garantáltan megoldja a problémádat, amennyiben nincs az új szkript létrehozására valamilyen ellenérved.

Futtatás új Bash processzben

[Tartalom]

FIGYELEM: A példákban használt $USER változó nem minden rendszeren tartalmazza a felhasználó nevét. Ebben az esetben a $(id -nu) írható helyette, vagy átadható az értéke a USER változónak:

export USER="$(id -nu)"

Lássuk először hogy nézne ki egy egyszerű függvény a sudo parancccsal, természetesen a nem működő módszerrel. Legyen a példa egy saját setPermissions függvény:

#!/bin/bash

function setPermissions() {
  echo "I am $USER"
  echo "Permissions: $1"
  echo "Target: $2"
}

echo "I am $USER"
echo "Run setPermissions() with sudo"
echo

sudo setPermissions "r w x" "/path with spaces/target"
## Output:
# I am rimelek
# Run setPermissions() with sudo
#
# [sudo] password for rimelek:
# sudo: setPermissions: command not found

A setPermissions parancs nem található a Bash szerint, mivel a sudo-nak csak fájlt lehet átadni paraméterként. A megoldás alapja valójában igen egyszerű. Ha nem hozol létre saját fájlt, használj egy már létezőt. Magát a "bash" programot. Az utasításokat függvény nélkül is át lehet adni a Bash-nek:

#!/bin/bash

setPermissions='
  echo "I am $USER"
  echo "Permissions: $1"
  echo "Target: $2"
'

echo "I am $USER"
echo "Run setPermissions() with sudo"
echo

sudo bash -c "$setPermissions" -- "r w x" "/path with spaces/target"

Ez persze még nem az igazi. Az utasítások bonyolultabbak is lehetnek. Mégis csak jó lenne egy függvény. A baj csak az, hogy az új bash processzben az előtte definiált függvény nem fog létezni.

Függvénydefiníció másolása

[Tartalom]

Ahogy a StackExcange fórumon felhívják rá a figyelmet, a declare paranccsal kiexportálhatod az eredeti függvény definícióját az alábbi módon:

declare -f setPermissions

Ekkor viszont már nem a bash-nek kell átadni a paramétereket, hanem a bash segítségével meghívott függvénynek. Az alábbira változik az utastíás:

# ...
setPermissions="$(declare -f setPermissions)"
sudo bash -c "$setPermissions; setPermissions 'r w x' '/path with spaces/target'"

Figyelj az idézőjelekre! A fenti megoldással a függvény deklarációja egy hasonló nevű változóba került, a bash paramétereként átadva az új processzben lefut, azaz ott is deklarálja a függvényt, majd meg is hívja. Bonyolultabb paraméterekkel talán az alábbi forma kezelhetőbb lesz. Válaszd, amelyik számodra kényelmesebb:

# ...
setPermissions="$(declare -f setPermissions)"
sudo bash -c "$setPermissions; setPermissions \"\$@\"" -- "r w x" "/path with spaces/target"

Függvények külső fájlból betöltése

[Tartalom]

Az előző megoldás ötletes, de képzeld el, amikor egy függvény meghív egy másikat, ami szintén egy másikat és így tovább. Neked az összes szükséges függvény definicióját át kell másolnod a sudo-val indított Bash-be. Azt pedig neked kell tudnod, hogy melyiknek milyen függőségei vannnak. Ehelyett viszont megteheted, hogy eleve külön fájlba, például egy functions.sh nevű shell szkriptbe mozgatod a függvényeidet, amit később más szkriptekben betölthetsz. Ha szeretnéd mégis a függvényeket elszeparálni több fájlba, annak sincs akadálya. Szükség esetén pedig akár készíthetsz egy, az összes függvényfájlt betöltő közös fájlt is. Megfontolandó, hogy érdemes-e, de a lehetőség adott.

Legyen tehát a functions.sh tartalma a következő:

SOURCED_FILENAME="${BASH_SOURCE[0]}"

function runAs() {
  local executor="$1"
  local functionName="$2"
  shift 2

  local command='source "'$SOURCED_FILENAME'"; '$functionName' "$@"'
  sudo -u "$executor" bash -c "$command" -- "$@"
}

function setPermissions() {
  echo "I am $USER"
  echo "Permissions: $1"
  echo "Target: $2"
}

  • Az első sorban a $BASH_SOURCE nevű tömb első elemére hivatkoztam, amiben a betöltött fájl neve van. Jelenleg a functions.sh. Ez azért szükséges, mert a sudo-val futtatott bash-nek is be kell töltenie ugyanezeket a függvényeket, hogy minden definíció elérhető legyen.

  • A runAs függvény a sudo helyett használható majd. A sudo-nak viszont van egy -u paramétere. A felhasználó, akinek a nevében futtatni kell az utasításokat. Ez persze opcionális, de így egy kicsit még okosabb lesz a függvény.

  • A command változóba átkerül a teljes futtatandó parancs, aminek része az is, hogy a $@ használatával a bash-nek átadott argumentumokat is tovább kell dobni a függvénynek. Azokat az argumentumokat viszont eleve a függvény argumentumai közül kell venni, kivéve az első kettőt. Ha nem érted, miért kellett kétszer is használni a kukac jelet, az előző cikkből megtudhatod.

Ezek után az alábbi módon futtathatod a függvényeidet egy új szkriptből:

#!/bin/bash

set -eu

# https://unix.stackexchange.com/questions/269078/executing-a-bash-script-function-with-sudo

source "functions.sh"

echo "I am $USER"
echo "Run setPermissions() with sudo"
echo
runAs root setPermissions "r w x" "/path with spaces/target"
## Output:
# I am rimelek
# Run setPermissions() with sudo
#
# [sudo] password for rimelek:
# I am root
# Permissions: r w x
# Target: /path with spaces/target

Bár nem gyakori, de ha arra is fel szeretnél készülni, hogy valaki átnevezi a ''root" felhasználót, add át az azonosító számot helyette:

runAs '#0' setPermissions "r w x" "/path with spaces/target"

Futtatás, mint nem adminisztrátor

[Tartalom]

Akár függvény, akár szkript, a program mélyén sudozás azt eredményezi, hogy nem a szkript indításának elején fogsz jelszó promptot kapni, hanem amikor a futtatás során az adott kód következik. Ezt elkerülheted, ha eleve root-ként futtatod a programot, akkor viszont lesznek olyan műveletek, amiket nem szeretnél root-ként futtatni. Például azért, mert a kérdéses művelet figyelembe veszi a futtató felhasználót és annak környezetét konfigurálja át, vagy a számára elérhető erőforrásokkall dolgozik. Az is lehet, hogy csak szeretnéd, ha a létrehozott új fájlokhoz nem csak a root-nak lenne jogosultsága.

Ekkor a kódodat úgy kell felépítened, hogy feltételezze, vagy akár kikényszerítse, hogy root felhasználóval futtassák, majd a runAs függvénnyel akkor dolgozol, amikor épp nem root felhasználóval kell futtatni egy utasítást. A másik lehetőség a jogosultságok vagy környezeti változók módosítása lenne, ami talán egy kicsivel több meló, de adott esetben lehet, hogy pont erre lesz szükséged.

A sudo használatával egy SUDO_USER nevű változó is létrejön, amit felhasználhatsz arra, hogy bizonyos utasításokat annak a felhasználónak anevében futtass, aki a sudo parancsot használva root jogot szerzett.

executor="$USER"
if [ -n "$SUDO_USER" ]; then
  executor="$SUDO_USER"
fi
sudo -u "$executor" bash -c "$command" -- "$@"

Összefoglalás

[Tartalom]

Most már több módod is van arra, hogy adminisztrátori felhasználóval futtass egy függvényt, de érdemes nagyon megfontolni, szükséges-e valóban ezt a komplexitást bevinni a programodba, vagy elég csak a "problémás" függvényeket külön szkriptekbe kiszervezni.

A tárgyalt megoldások segítségével bármilyen felhasználó nevében futtathatsz programokat, de arra figyelned kell, hogy az sudo-val indított bash processzben a környezeti változók is megváltoznak, hacsak nem használod a -E kapcsolót. Érdemes nem globális változókra alapozni a függvények működését argumentumok helyett, ahol viszont szükséges, gondoskodj a változók beállításáról. Ez történhet a runAs függvénybe beépített módon, vagy akár egy dinamikusan generált, változókat tartalmazó fájl segítségével, ami fájlt akár egy újabb argumentumként is átadhatsz a függvénynek. A dinamikusan generált fájl készítéséhez az envsubst parancsnak kell utánanézned. Szükség esetén pedig telepíteni is kellhet, ha még nem elérhető a rendszereden.

Ha szeretnél további cikkeket Bash témakörben, jelezd kommentben vagy like-old a cikket. Másról szeretnél olvasni? Írd meg a kívánságod, és ha nem is hármat, de igyekszem teljesíteni. Ne felejtsd el másokkal is megosztani a cikket és bátran árassz el észrevételekkel, javaslatokkal. Kellemes tanulást a következő alkalomig is!

Források

[Tartalom]

Megosztás/Mentés