0Pricing
DevOps Bootcamp · Lekcja

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: ALL

Rezygnowanie 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 polecenie
  • runuser -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ą sudo zawsze 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 += PATH lub env_keep += LD_*
  • sudo -E należ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.sh

Pozwala 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:

  1. sudo jako takie — /var/log/auth.log (Debian/Ubuntu) lub /var/log/secure (RHEL) automatycznie rejestruje każde wywołanie sudo
  2. 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 sudo do pojedynczych poleceń — nigdy nie podnoś uprawnień całego skryptu; stosuj sudo tylko 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ą runuser lub sudo -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 $PATH w 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

  1. Zapobieganie wstrzykiwaniu poleceń i argumentów
  2. Bezpieczne obchodzenie się z sekretami i higiena środowiska
  3. Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo
  4. Analiza statyczna i audyt za pomocą ShellCheck
← Powrót do DevOps Bootcamp