Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo
Odbieraj uprawnienia, ściśle ograniczaj reguły sudo i sprawdzaj efektywny UID przed ryzykownymi operacjami.
Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo to bezpłatna lekcja DevOps Bootcamp na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DevOps Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.
Dlaczego zasada najmniejszych uprawnień ma znaczenie w skryptach powłoki
Większość naruszeń bezpieczeństwa w automatyzacji nie wynika z wykorzystania egzotycznych luk, lecz z faktu, że skrypty działają z większymi uprawnieniami, niż potrzebują. Zadanie cron uruchomione jako root, które musi jedynie rotować plik dziennika, to proszenie się o problemy.
Zasada najmniejszych uprawnień głosi, że każdy proces powinien działać wyłącznie z uprawnieniami wymaganymi do wykonania swojego zadania — i z żadnymi dodatkowymi. W skryptach Bash oznacza to:
- Uruchamianie skryptu jako użytkownik bez uprawnień administracyjnych, gdy tylko jest to możliwe
- Podnoszenie uprawnień do root wyłącznie dla konkretnych poleceń, które tego wymagają
- Rezygnowanie z podwyższonych uprawnień natychmiast po zakończeniu uprzywilejowanych działań
- Nigdy nieprzechowywanie ani nieprzejmowanie danych uwierzytelniających poza zakresem, w którym są potrzebne
W tej lekcji omówiono konkretne techniki: ograniczanie zakresu sudo, rezygnowanie z uprawnień za pomocą su, ochronę przez walidację UID oraz wzmacnianie konfiguracji sudoers — tworząc zdyscyplinowany model uprawnień dla skryptów produkcyjnych.
Sprawdzanie efektywnego UID przed ryzykownymi operacjami
Przed każdym blokiem kodu, który rzeczywiście wymaga uprawnień root, skrypt powinien sprawdzić, czy działa z oczekiwanym efektywnym UID. Nie należy zakładać — zawsze trzeba to zweryfikować.
$EUID to specjalna zmienna Bash zawierająca identyfikator efektywnych uprawnień użytkownika bieżącego procesu. Root zawsze ma EUID równy 0. Sprawdzenie tej wartości na początku skryptu — lub wokół uprzywilejowanego bloku — zapobiega przypadkowemu uruchomieniu z niewłaściwą tożsamością.
Należy użyć następującego wzorca ochronnego:
#!/usr/bin/env bash
set -euo pipefail
# Guard: this script must NOT run as root.
if [[ "$EUID" -eq 0 ]]; then
echo "ERROR: Do not run this script as root. Use a normal user account." >&2
exit 1
fi
echo "Running as UID $EUID — proceeding safely."Wymaganie uprawnień root tylko wtedy, gdy są potrzebne
Niektóre skrypty rzeczywiście wymagają uprawnień root. W takim przypadku należy odwrócić działanie ochrony: zakończyć skrypt od razu, jeśli brakuje uprawnień root, zamiast pozwolić mu dotrzeć do uprzywilejowanego wywołania systemowego i zgłosić niejasny błąd uprawnień w trakcie działania.
Połączenie wczesnego zakończenia z pomocnym komunikatem o użyciu sprawia, że skrypty same dokumentują sposób działania:
#!/usr/bin/env bash
set -euo pipefail
require_root() {
if [[ "$EUID" -ne 0 ]]; then
echo "ERROR: $(basename "$0") must be run as root." >&2
echo " Try: sudo $(basename "$0") $*" >&2
exit 1
fi
}
require_root "$@"
echo "Root confirmed (EUID=0). Starting privileged work..."Ograniczanie sudo do pojedynczych poleceń
Najczęstszym błędem jest umieszczenie sudo na początku skryptu, a następnie uruchamianie wszystkiego jako root. Zamiast tego należy stosować sudo wyłącznie do dokładnie tego polecenia, które go wymaga — cała reszta powinna działać jako zwykły użytkownik.
Ogranicza to zasięg potencjalnych szkód: jeśli osoba atakująca wstrzyknie kod do skryptu, z uprawnieniami root będzie mogła wykonać tylko te fragmenty, przed którymi znajduje się sudo.
Poniżej porównano dwa wzorce. Drugi jest zdecydowanie bezpieczniejszy:
#!/usr/bin/env bash
set -euo pipefail
# BAD: escalate early, do everything as root (avoid this)
# sudo bash -c '
# cp config.conf /etc/app/config.conf
# chown app:app /etc/app/config.conf
# systemctl restart app
# '
# GOOD: escalate only for the commands that require it
LOCAL_CONF="./config.conf"
DEST="/etc/app/config.conf"
# Unprivileged: validate the config before touching anything as root
if ! grep -q '^[[:space:]]*\[main\]' "$LOCAL_CONF"; then
echo "ERROR: config.conf is missing [main] section" >&2
exit 1
fi
# Privileged: only these three commands run under sudo
sudo cp "$LOCAL_CONF" "$DEST"
sudo chown app:app "$DEST"
sudo systemctl restart app
echo "Config deployed and service restarted."Tworzenie restrykcyjnych reguł sudoers
Wywołanie sudo somecommand w skrypcie jest bezpieczne tylko wtedy, gdy plik sudoers zezwala dokładnie na to polecenie — i na nic więcej. Należy unikać reguł takich jak ALL=(ALL) NOPASSWD: ALL dla kont usług.
Zamiast tego należy ograniczyć reguły do konkretnych poleceń z konkretnymi argumentami, używając składni bezpiecznej dla visudo. Kluczowe pola reguły sudoers:
- User — kto może wywoływać sudo
- Host — na którym komputerze (dla przenośności należy użyć
ALL) - RunAs — jaką tożsamość przyjąć (niemal zawsze
root) - Command — pełna ścieżka bezwzględna, opcjonalnie z dosłownymi argumentami
Przykład restrykcyjnych reguł dla konta usługi wdrożeniowej (deployer):
# /etc/sudoers.d/deployer (edit with: sudo visudo -f /etc/sudoers.d/deployer)
#
# Allow 'deployer' to restart exactly one service — nothing else
deployer ALL=(root) NOPASSWD: /usr/bin/systemctl restart app
# Allow copying a config file to a fixed destination only
deployer ALL=(root) NOPASSWD: /usr/bin/cp /home/deployer/staging/config.conf /etc/app/config.conf
# Allow chown of that specific file only
deployer ALL=(root) NOPASSWD: /usr/bin/chown app\:app /etc/app/config.conf
# NEVER do this — gives full root shell:
# deployer ALL=(ALL) NOPASSWD: ALLRezygnowanie z uprawnień za pomocą su i runuser
Gdy skrypt rozpoczyna działanie jako root (np. uruchomiony przez system init lub cron działający jako root), ale większość pracy powinna odbywać się jako użytkownik bez uprawnień administracyjnych, należy jawnie zrezygnować z uprawnień, zamiast uruchamiać cały skrypt jako root.
Służą do tego dwa narzędzia:
su -s /bin/bash -c 'command' username— uruchamia powłokę jako username i wykonuje polecenierunuser -u username -- command args— preferowane w systemie Linux do przełączania użytkownika w skryptach należących do roota; jest prostsze niżsu
Poniższy wzorzec pokazuje należący do roota wrapper wdrożeniowy, który przełącza się na użytkownika app w celu wykonania właściwej logiki aplikacji:
#!/usr/bin/env bash
# This script is called by systemd as root during pre-deployment
set -euo pipefail
APP_USER="app"
DEPLOY_DIR="/opt/myapp"
# Step 1: privileged — fix ownership of deploy directory
chown -R "${APP_USER}:${APP_USER}" "$DEPLOY_DIR"
# Step 2: drop to app user for the actual migration/startup logic
# runuser is available on most modern Linux systems
runuser -u "$APP_USER" -- bash -c "
cd $DEPLOY_DIR
./bin/migrate.sh
./bin/start.sh
"
echo "Deploy complete. Privileged wrapper exiting."Używanie sudo -u do uruchamiania pojedynczego polecenia jako inny użytkownik
Nie zawsze trzeba przełączać się do pełnej sesji powłoki. sudo -u username command uruchamia pojedyncze polecenie jako wskazany użytkownik, a następnie przywraca tożsamość wywołującego. Jest to przydatne do modyfikowania plików należących do konta usługi bez przyznawania temu kontu dostępu interaktywnego.
Należy połączyć to z regułą sudoers, która zezwala dokładnie na tę kombinację użytkownika i polecenia:
#!/usr/bin/env bash
set -euo pipefail
# Scenario: deploy script runs as 'deployer'; DB migrations must run as 'postgres'
# sudoers entry needed:
# deployer ALL=(postgres) NOPASSWD: /opt/app/bin/run_migrations.sh
DB_MIGRATION_SCRIPT="/opt/app/bin/run_migrations.sh"
if [[ ! -x "$DB_MIGRATION_SCRIPT" ]]; then
echo "ERROR: migration script not found or not executable: $DB_MIGRATION_SCRIPT" >&2
exit 1
fi
echo "Running DB migrations as postgres user..."
sudo -u postgres "$DB_MIGRATION_SCRIPT"
echo "Migrations done. Returning to deployer context (EUID=$EUID)."Zapobieganie eskalacji uprawnień przez zmienne środowiskowe
Jednym z mniej oczywistych obszarów ataku są zmienne środowiskowe odziedziczone przez sesję sudo. Domyślnie sudo resetuje środowisko, ale nieprawidłowo skonfigurowane wyjątki env_keep lub zastąpienia env_reset mogą przekazać do uprzywilejowanych poleceń zmienne kontrolowane przez osobę atakującą, takie jak LD_PRELOAD, PATH lub PYTHONPATH.
Najlepsze praktyki:
- W skryptach uruchamianych za pomocą
sudozawsze należy używać ścieżek bezwzględnych — nigdy nie należy polegać na$PATH - Należy przekazywać tylko jawnie potrzebne zmienne:
sudo env VAR=value /path/to/cmd - W pliku sudoers należy unikać
env_keep += PATHlubenv_keep += LD_* sudo -Enależy stosować tylko wtedy, gdy środowisko wywołujące jest w pełni kontrolowane i zaufane
#!/usr/bin/env bash
set -euo pipefail
# BAD: relies on $PATH — attacker who controls PATH can hijack 'cp'
# sudo cp config.conf /etc/app/
# GOOD: absolute paths for every command called under elevated context
SUDO_BIN="/usr/bin/sudo"
CP_BIN="/usr/bin/cp"
CHOWN_BIN="/usr/bin/chown"
SYSTEMCTL_BIN="/usr/bin/systemctl"
"$SUDO_BIN" "$CP_BIN" ./config.conf /etc/app/config.conf
"$SUDO_BIN" "$CHOWN_BIN" app:app /etc/app/config.conf
"$SUDO_BIN" "$SYSTEMCTL_BIN" restart app
echo "Deployed with hardened absolute-path invocations."Blokowanie sudo za pomocą walidacji argumentów poleceń
Nawet gdy reguła sudoers zezwala na konkretny skrypt, można przekazać mu dowolne argumenty, chyba że reguła również je ogranicza. Częsta pułapka:
deployer ALL=(root) NOPASSWD: /opt/scripts/manage.shPozwala to na wykonanie sudo /opt/scripts/manage.sh restart — ale również sudo /opt/scripts/manage.sh --arbitrary-flag. Jeśli manage.sh bez sprawdzania przekazuje argumenty do uprzywilejowanych poleceń podrzędnych, stanowi to problem.
Należy stosować ochronę wielowarstwową: walidować argumenty wewnątrz uprzywilejowanego skryptu, a także w sudoers:
#!/usr/bin/env bash
# /opt/scripts/manage.sh — called via sudo; must validate its own args
set -euo pipefail
# Allowlist of valid actions
declare -A ALLOWED_ACTIONS=(
[restart]=1
[status]=1
[reload]=1
)
ACTION="${1:-}"
if [[ -z "$ACTION" ]]; then
echo "Usage: $(basename "$0") <restart|status|reload>" >&2
exit 1
fi
if [[ -z "${ALLOWED_ACTIONS[$ACTION]:-}" ]]; then
echo "ERROR: Unknown action '${ACTION}'. Allowed: ${!ALLOWED_ACTIONS[*]}" >&2
exit 2
fi
/usr/bin/systemctl "$ACTION" app
echo "Action '$ACTION' executed successfully."Tymczasowe podniesienie uprawnień z pułapką sprzątającą
Gdy skrypt musi na krótko uzyskać dostęp z uprzywilejowanymi uprawnieniami do pliku, danych uwierzytelniających lub zasobu, należy użyć funkcji Bash trap, aby zagwarantować wykonanie sprzątania nawet w przypadku błędu lub odebrania sygnału. Zapobiega to wyciekowi uprawnień — na przykład pozostawieniu tymczasowego pliku binarnego setuid lub gniazda należącego do użytkownika root po awarii skryptu.
Poniższy schemat tworzy plik tymczasowy jako root, używa go, a następnie usuwa — gwarantuje to pułapka ustawiona na EXIT:
#!/usr/bin/env bash
set -euo pipefail
# Must run as root for this demo
if [[ "$EUID" -ne 0 ]]; then
echo "Run as root" >&2; exit 1
fi
TMP_SECRET=""
cleanup() {
local exit_code=$?
if [[ -n "$TMP_SECRET" && -f "$TMP_SECRET" ]]; then
# Overwrite before deletion to reduce forensic recovery risk
shred -u "$TMP_SECRET" 2>/dev/null || rm -f "$TMP_SECRET"
echo "[cleanup] Removed privileged temp file." >&2
fi
exit "$exit_code"
}
trap cleanup EXIT INT TERM
# Create a root-owned temp file for a short-lived secret
TMP_SECRET="$(mktemp /tmp/deploy_secret.XXXXXXXX)"
chmod 600 "$TMP_SECRET"
# Simulate fetching a secret into the temp file
echo "super-secret-token" > "$TMP_SECRET"
# Use the secret (e.g., pass to a sub-command via file descriptor)
/usr/bin/some-privileged-tool --key-file "$TMP_SECRET"
echo "Privileged operation complete."Audytowanie i rejestrowanie uprzywilejowanych działań
Zasadę minimalnych uprawnień łatwiej egzekwować, gdy każde podniesienie uprawnień jest rejestrowane wraz z kontekstem: kto wykonał daną czynność, co zrobił, kiedy i dlaczego. Należy połączyć dwie warstwy:
- sudo jako takie —
/var/log/auth.log(Debian/Ubuntu) lub/var/log/secure(RHEL) automatycznie rejestruje każde wywołanie sudo - Rejestr audytowy na poziomie skryptu — należy zapisać ustrukturyzowany wpis na początku każdej funkcji uprzywilejowanej, aby intencja była zapisana obok dziennika systemowego
Użycie prostej funkcji do rejestrowania ustrukturyzowanych danych zapewnia spójność ścieżek audytowych i ułatwia wyszukiwanie za pomocą grep:
#!/usr/bin/env bash
set -euo pipefail
AUDIT_LOG="/var/log/app_deploy_audit.log"
log_privileged_action() {
local action="$1"
local reason="${2:-unspecified}"
local ts
ts="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
printf '{"ts":"%s","user":"%s","euid":%d,"action":"%s","reason":"%s"}\n' \
"$ts" "${SUDO_USER:-$USER}" "$EUID" "$action" "$reason" \
| sudo tee -a "$AUDIT_LOG" > /dev/null
}
# Each privileged step is logged before execution
log_privileged_action "cp_config" "deploy release v2.4.1"
sudo /usr/bin/cp ./config.conf /etc/app/config.conf
log_privileged_action "chown_config" "ensure app user owns config"
sudo /usr/bin/chown app:app /etc/app/config.conf
log_privileged_action "restart_service" "activate new config"
sudo /usr/bin/systemctl restart app
echo "Deployment complete. Audit entries written to $AUDIT_LOG"Sprawdzenie wiedzy: zakres sudo
Proszę sprawdzić swoją wiedzę na temat wykonywania skryptów Bash zgodnie z zasadą minimalnych uprawnień.
Podsumowanie: wykonywanie z minimalnymi uprawnieniami i zasady używania sudo
W tej lekcji opanowali Państwo techniki uruchamiania skryptów Bash z minimalnymi uprawnieniami wymaganymi na każdym etapie. Poniżej znajduje się krótkie podsumowanie najważniejszych zasad:
- Sprawdzaj za pomocą
$EUID— kończ działanie natychmiast, jeśli skrypt jest uruchomiony z niewłaściwą tożsamością, niezależnie od tego, czy należy odmówić uruchomienia jako root, czy tego wymagać - Ograniczaj zakres
sudodo pojedynczych poleceń — nigdy nie podnoś uprawnień całego skryptu; stosujsudotylko w wierszach, które rzeczywiście tego wymagają - Twórz precyzyjne reguły sudoers — określaj pełne ścieżki bezwzględne oraz dosłowne argumenty; unikaj symboli wieloznacznych i
ALL - Obniżaj uprawnienia za pomocą
runuserlubsudo -u— gdy skrypt uruchomiony jako root musi przekazać wykonanie użytkownikowi bez uprawnień, użyj właściwego narzędzia zamiast uruchamiać wszystko jako root - Używaj ścieżek bezwzględnych — nigdy nie polegaj na
$PATHw kodzie uprzywilejowanym; wpisuj ścieżki do plików binarnych na stałe, aby zapobiec przejęciu wykonywania - Sprawdzaj argumenty wewnątrz skryptów uprzywilejowanych — reguły sudoers są pierwszą linią obrony, ale nie jedyną; używaj list dozwolonych wartości
- Obsługuj pułapki i sprzątaj — używaj
trap cleanup EXIT, aby zagwarantować usunięcie tymczasowych zasobów uprzywilejowanych nawet w przypadku błędu - Rejestruj każde podniesienie uprawnień — ustrukturyzowane wpisy audytowe połączone z wbudowanym dziennikiem syslog sudo zapewniają możliwość prześledzenia każdej uprzywilejowanej czynności
Konsekwentne stosowanie tych praktyk zmniejsza powierzchnię ataku automatyzacji — od root przez cały czas do root wyłącznie tam, gdzie jest to możliwe do jednoznacznego uzasadnienia — co jest znakiem rozpoznawczym wzmocnionego skryptu Bash gotowego do użycia produkcyjnego.
Często zadawane pytania
Czy lekcja „Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo” jest bezpłatna?
Tak — pełny tekst „Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DevOps Bootcamp, przejdź na CoddyKit PRO. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.
Co nauczysz się w „Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo”?
Odbieraj uprawnienia, ściśle ograniczaj reguły sudo i sprawdzaj efektywny UID przed ryzykownymi operacjami. Ćwiczysz DevOps Bootcamp z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć DevOps Bootcamp?
Nie wymagamy żadnego doświadczenia. DevOps Bootcamp w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji DevOps Bootcamp?
Tak. Każda lekcja DevOps Bootcamp zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Zapobieganie wstrzykiwaniu poleceń i argumentów
- Bezpieczne obchodzenie się z sekretami i higiena środowiska
- Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo
- Analiza statyczna i audyt za pomocą ShellCheck