최소 권한 실행 및 sudo 관리
권한을 낮추고 sudo 규칙의 범위를 엄격히 제한하며 위험한 작업 전에 유효 UID를 검증합니다.
최소 권한 실행 및 sudo 관리은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
셸 스크립트에서 최소 권한이 중요한 이유
자동화에서 발생하는 대부분의 보안 침해는 기묘한 공격 때문이 아니라, 스크립트가 필요한 것보다 많은 권한으로 실행되기 때문입니다. 로그 파일 하나만 교체하면 되는 작업을 root로 실행하는 cron 작업은 사고가 발생하기 쉬운 상태입니다.
최소 권한 원칙은 모든 프로세스가 작업에 필요한 권한만 사용하고 그 이상은 사용하지 않아야 한다는 것입니다. Bash 스크립팅에서는 다음을 의미합니다.
- 가능하면 권한이 없는 사용자로 실행하기
- 필요한 특정 명령에 대해서만 root로 권한 상승하기
- 권한 상승 작업이 끝나는 즉시 권한 내리기
- 범위를 벗어난 인증 정보를 저장하거나 상속하지 않기
이 과정에서는 sudo 범위 지정, su를 통한 권한 내리기, UID 검증 보호 장치, sudoers 보안 강화와 같은 구체적인 기법을 살펴보며, 운영 환경 스크립트를 위한 체계적인 권한 모델을 구축합니다.
위험한 작업 전에 유효 UID 확인하기
실제로 root가 필요한 코드 블록을 실행하기 전에 스크립트는 예상한 유효 UID로 실행 중인지 확인해야 합니다. 절대 추측하지 말고 항상 단언하십시오.
$EUID는 현재 프로세스의 유효 사용자 ID를 담고 있는 Bash 특수 변수입니다. root의 EUID는 항상 0입니다. 스크립트 시작 부분이나 권한이 필요한 블록 주변에서 이를 확인하면 잘못된 사용자 ID로 실수로 실행되는 일을 방지할 수 있습니다.
다음 보호 패턴을 사용하십시오.
#!/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."필요할 때만 root임을 확인하기
일부 스크립트는 실제로 root가 필요합니다. 이 경우 보호 방식은 반대로 적용됩니다. root가 없으면 일찍 실패하여 스크립트가 권한이 필요한 시스템 호출에 도달한 뒤 실행 중 혼란스러운 권한 오류를 내게 하지 마십시오.
조기에 종료하고 유용한 사용법 메시지를 함께 표시하면 스크립트 자체가 사용법을 설명하는 역할도 하게 됩니다.
#!/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..."단일 명령으로 sudo 범위 제한하기
가장 흔한 실수는 스크립트 맨 위에 sudo를 넣고 모든 작업을 root로 실행하는 것입니다. 대신 sudo는 꼭 필요한 정확한 명령에만 적용하고, 나머지는 일반 사용자로 실행하십시오.
이렇게 하면 피해 범위를 제한할 수 있습니다. 공격자가 스크립트에 코드를 삽입하더라도 sudo가 앞에 붙지 않은 부분은 root 권한으로 실행할 수 없습니다.
아래 두 패턴을 비교해 보십시오. 두 번째 패턴이 훨씬 안전합니다.
#!/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."엄격한 sudoers 규칙 작성하기
스크립트에서 sudo somecommand를 호출하는 것이 안전하게 작동하려면 sudoers 파일이 해당 명령만 정확히 허용하고 그 이상은 허용하지 않도록 구성되어야 합니다. 서비스 계정에 ALL=(ALL) NOPASSWD: ALL과 같은 규칙을 사용하지 마십시오.
대신 visudo로 안전하게 편집할 수 있는 구문을 사용하여 특정 인수를 받는 특정 명령으로 규칙을 제한하십시오. sudoers 규칙의 주요 필드는 다음과 같습니다.
- 사용자 — sudo를 호출할 수 있는 사용자
- 호스트 — 어느 컴퓨터에서 사용할 수 있는지(이식성을 위해
ALL사용) - RunAs — 어떤 사용자 ID를 사용할지(거의 항상
root) - 명령 — 선택적으로 리터럴 인수를 포함하는 완전한 절대 경로
배포 서비스 계정(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: ALLsu와 runuser로 권한 내리기
스크립트가 root로 시작하지만(예: root로 실행되는 시스템 초기화 작업이나 cron이 실행한 경우) 대부분의 작업은 권한이 없는 사용자로 수행해야 한다면, 전체 스크립트를 root로 실행하지 말고 명시적으로 권한을 내리십시오.
이를 위한 두 가지 도구는 다음과 같습니다.
su -s /bin/bash -c 'command' username— username으로 셸을 시작하고 명령을 실행합니다.runuser -u username -- command args— root 소유 스크립트 안에서 사용자 전환을 수행할 때 Linux에서 권장됩니다.su보다 깔끔합니다.
다음 패턴은 root가 소유한 배포 래퍼가 실제 애플리케이션 로직을 실행할 때 app 사용자로 권한을 내리는 모습을 보여 줍니다.
#!/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."sudo -u로 다른 사용자로 단일 명령 실행하기
항상 전체 셸 세션으로 권한을 내릴 필요는 없습니다. sudo -u username command는 지정한 사용자로 단일 명령을 실행한 다음 호출한 사용자 ID로 돌아옵니다. 서비스 계정이 소유한 파일을 수정하면서 해당 계정에 대화형 접근 권한은 부여하지 않을 때 유용합니다.
이 기능을 정확히 해당 사용자와 명령의 조합만 허용하는 sudoers 규칙과 함께 사용하십시오.
#!/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)."환경 변수를 통한 권한 상승 방지하기
미묘한 공격 표면 중 하나는 sudo 세션이 상속하는 환경 변수입니다. 기본적으로 sudo는 환경을 초기화하지만, 잘못 구성된 env_keep 또는 env_reset 재정의 설정은 공격자가 제어하는 LD_PRELOAD, PATH, PYTHONPATH와 같은 변수를 권한이 높은 명령에 전달할 수 있습니다.
권장 사항:
sudo로 실행되는 스크립트에서는 항상 절대 경로를 사용하고$PATH에 의존하지 마십시오.- 명시적으로 필요한 변수만 전달하십시오. 예:
sudo env VAR=value /path/to/cmd - sudoers에서
env_keep += PATH또는env_keep += LD_*를 사용하지 마십시오. - 호출 환경을 완전히 제어하고 신뢰하는 경우에만
sudo -E를 사용하십시오.
#!/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."명령 인수 검증으로 sudo 잠그기
sudoers 규칙이 특정 스크립트를 허용하더라도, 규칙에서 인수까지 제한하지 않으면 해당 스크립트에 임의의 인수를 전달할 수 있습니다. 흔한 함정은 다음과 같습니다.
deployer ALL=(root) NOPASSWD: /opt/scripts/manage.sh이 규칙은 sudo /opt/scripts/manage.sh restart를 허용하지만 sudo /opt/scripts/manage.sh --arbitrary-flag도 허용합니다. manage.sh가 인수를 검증 없이 권한이 필요한 하위 명령에 전달한다면 문제가 발생합니다.
심층 방어를 적용하십시오. 권한이 필요한 스크립트 내부와 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."정리 트랩을 사용한 임시 권한 상승
스크립트가 권한이 필요한 파일, 자격 증명 또는 리소스를 잠시 보유해야 하는 경우, 오류나 신호가 발생해도 정리가 수행되도록 Bash의 trap을 사용하십시오. 이렇게 하면 스크립트가 충돌할 때 임시 setuid 바이너리나 root 소유 소켓이 남는 등 권한이 유출되는 것을 방지할 수 있습니다.
아래 패턴은 root 권한으로 임시 파일을 만들고, 사용한 다음 삭제합니다. 이 동작은 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."권한이 필요한 작업 감사 및 기록
모든 권한 상승 이벤트가 상황 정보와 함께 기록되면 최소 권한 원칙을 더 쉽게 적용할 수 있습니다. 누가 무엇을 언제, 왜 실행했는지 기록하는 것입니다. 다음 두 계층을 결합하십시오.
- sudo 자체 — (Debian/Ubuntu)의
/var/log/auth.log또는 (RHEL)의/var/log/secure에는 모든 sudo 호출이 자동으로 기록됩니다. - 스크립트 수준 감사 로그 — 모든 권한이 필요한 함수의 시작 부분에 구조화된 항목을 기록하여 의도가 시스템 로그와 함께 남도록 합니다.
간단한 구조화 로그 함수를 사용하면 감사 추적을 일관되고 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"지식 확인: sudo 범위 지정
Bash 스크립트에서 최소 권한으로 실행하는 방법을 얼마나 이해했는지 확인해 보십시오.
복습: 최소 권한 실행과 sudo 원칙
이 레슨에서는 각 단계에서 필요한 최소 권한으로 Bash 스크립트를 실행하는 기법을 익혔습니다. 다음은 핵심 원칙을 간결하게 정리한 내용입니다.
$EUID로 확인하기 — 스크립트가 잘못된 사용자 ID로 실행되면 즉시 실패하게 하십시오. root 실행을 거부해야 하는 경우와 root 실행이 필요한 경우 모두에 해당합니다.- 개별 명령에
sudo범위 지정하기 — 전체 스크립트의 권한을 높이지 말고, 실제로 권한이 필요한 줄에만sudo를 적용하십시오. - 엄격한 sudoers 규칙 작성하기 — 완전한 절대 경로와 명시적인 인수를 지정하고, 와일드카드와
ALL은 피하십시오. runuser또는sudo -u로 권한 낮추기 — root로 시작한 스크립트가 권한 없는 사용자에게 작업을 넘겨야 할 때는 모든 작업을 root로 실행하지 말고 적절한 도구를 사용하십시오.- 절대 경로 사용하기 — 권한이 필요한 코드에서
$PATH에 의존하지 마십시오. 가로채기를 방지하려면 바이너리 경로를 하드코딩하십시오. - 권한이 필요한 스크립트 내부에서 인수 검증하기 — sudoers 규칙은 첫 번째 방어선일 뿐 유일한 방어선이 아닙니다. 허용 목록을 사용하십시오.
- 트랩을 설정하고 정리하기 — 오류가 발생하더라도 임시 권한 리소스가 반드시 삭제되도록
trap cleanup EXIT을 사용하십시오. - 모든 권한 상승 기록하기 — 구조화된 감사 항목을 내장 sudo syslog와 결합하면 권한이 필요한 모든 작업을 추적할 수 있습니다.
이러한 방식을 일관되게 적용하면 자동화의 공격 표면을 항상 root인 상태에서 입증할 수 있는 경우에만 root인 상태로 줄일 수 있습니다. 이는 강화된 프로덕션 수준 Bash의 특징입니다.
자주 묻는 질문
“최소 권한 실행 및 sudo 관리” 강의는 무료인가요?
네 — “최소 권한 실행 및 sudo 관리” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“최소 권한 실행 및 sudo 관리”에서 뭘 배우나요?
권한을 낮추고 sudo 규칙의 범위를 엄격히 제한하며 위험한 작업 전에 유효 UID를 검증합니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“최소 권한 실행 및 sudo 관리” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 명령 및 인수 주입 방지
- 안전한 비밀 정보 처리 및 환경 관리
- 최소 권한 실행 및 sudo 관리
- ShellCheck를 활용한 정적 분석 및 감사