Ejecución con mínimos privilegios y disciplina con sudo
Reduzca privilegios, limite estrictamente las reglas de sudo y valide el UID efectivo antes de realizar operaciones arriesgadas.
Ejecución con mínimos privilegios y disciplina con sudo es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.
Por qué es importante el mínimo privilegio en los scripts de shell
La mayoría de las brechas de seguridad en la automatización no se producen por exploits exóticos, sino porque los scripts se ejecutan con más privilegios de los necesarios. Un trabajo de cron que se ejecuta como root y solo necesita rotar un archivo de registro es un accidente esperando a ocurrir.
El principio de mínimo privilegio establece que cada proceso debe operar usando únicamente los permisos necesarios para realizar su tarea, y ninguno más. En los scripts de Bash, esto significa:
- Ejecutarse como un usuario sin privilegios siempre que sea posible
- Elevarse a root solo para los comandos específicos que lo requieran
- Abandonar los privilegios en cuanto termine el trabajo elevado
- No almacenar ni heredar credenciales fuera de su ámbito
En esta lección se explican técnicas concretas: delimitación de sudo, abandono de privilegios con su, protecciones de validación de UID y refuerzo de sudoers, para crear un modelo disciplinado de privilegios en scripts de producción.
Comprobación del UID efectivo antes de operaciones de riesgo
Antes de cualquier bloque de código que realmente requiera root, el script debe verificar que se ejecuta con el UID efectivo esperado. Nunca lo dé por supuesto; compruébelo siempre.
$EUID es una variable especial de Bash que contiene el ID de usuario efectivo del proceso actual. Root siempre tiene un EUID igual a 0. Comprobarlo al principio del script, o alrededor de un bloque privilegiado, evita la ejecución accidental con una identidad incorrecta.
Use este patrón de protección:
#!/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."Exigir root solo cuando sea necesario
Algunos scripts necesitan root legítimamente. En ese caso, la protección se invierte: falle pronto si no se está ejecutando como root, en lugar de permitir que el script llegue a una llamada al sistema privilegiada y produzca un error de permisos confuso a mitad de la ejecución.
Combinar la salida temprana con un mensaje de uso útil hace que los scripts se documenten por sí mismos:
#!/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..."Limitar sudo a comandos individuales
El error más común consiste en colocar sudo al principio de un script y ejecutar después todo como root. En su lugar, aplique sudo únicamente al comando exacto que lo necesita; todo lo demás se ejecuta como su usuario habitual.
Esto limita el alcance del daño: si un atacante inyecta código en su script, solo podrá ejecutar con permisos de root las partes que no tengan sudo delante.
Compare los dos patrones siguientes. El segundo es mucho más seguro:
#!/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."Redacción de reglas estrictas de sudoers
Invocar sudo somecommand en un script solo es seguro si el archivo sudoers está configurado para permitir exactamente ese comando, y nada más. Evite reglas como ALL=(ALL) NOPASSWD: ALL para las cuentas de servicio.
En su lugar, restrinja las reglas a comandos específicos con argumentos específicos usando la sintaxis segura para visudo. Campos clave de una regla de sudoers:
- Usuario: quién puede invocar sudo
- Host: en qué máquina (use
ALLpara facilitar la portabilidad) - RunAs: qué identidad se debe asumir (casi siempre
root) - Comando: ruta absoluta completa, opcionalmente con argumentos literales
Ejemplos de reglas estrictas para una cuenta de servicio de implementación (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: ALLAbandono de privilegios con su y runuser
Cuando un script se inicia como root (por ejemplo, mediante un sistema de inicio o un cron que se ejecuta como root), pero la mayor parte del trabajo debería realizarse como un usuario sin privilegios, abandone los privilegios explícitamente en lugar de ejecutar todo el script como root.
Dos herramientas para ello:
su -s /bin/bash -c 'command' username: inicia un shell como username y ejecuta el comandorunuser -u username -- command args: opción preferida en Linux para cambiar de usuario dentro de scripts propiedad de root; es más limpia quesu
El siguiente patrón muestra un envoltorio de implementación propiedad de root que cambia al usuario app para ejecutar la lógica real de la aplicación:
#!/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."Uso de sudo -u para ejecutar un comando individual como otro usuario
No siempre necesita cambiar a una sesión completa de shell. sudo -u username command ejecuta un único comando como el usuario especificado y después vuelve a la identidad del usuario que lo invocó. Esto resulta útil para modificar archivos propiedad de una cuenta de servicio sin conceder a esa cuenta acceso interactivo.
Combine esto con una regla de sudoers que permita exactamente esa combinación de usuario y comando:
#!/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)."Cómo evitar la escalada de privilegios mediante variables de entorno
Existe una superficie de ataque sutil: las variables de entorno heredadas por una sesión de sudo. De forma predeterminada, sudo restablece el entorno, pero las configuraciones incorrectas de env_keep o las anulaciones de env_reset pueden pasar variables controladas por un atacante, como LD_PRELOAD, PATH o PYTHONPATH, a comandos privilegiados.
Buenas prácticas:
- Use siempre rutas absolutas en scripts que se ejecuten mediante
sudo; nunca dependa de$PATH - Pase únicamente las variables que necesite explícitamente:
sudo env VAR=value /path/to/cmd - En sudoers, evite
env_keep += PATHoenv_keep += LD_* - Use
sudo -Esolo cuando controle completamente y confíe en el entorno desde el que se realiza la llamada
#!/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."Refuerzo de sudo mediante la validación de argumentos de comandos
Incluso cuando una regla de sudoers permite un script específico, se le pueden pasar argumentos arbitrarios, a menos que la regla también los restrinja. Un error habitual:
deployer ALL=(root) NOPASSWD: /opt/scripts/manage.shEsto permite sudo /opt/scripts/manage.sh restart, pero también sudo /opt/scripts/manage.sh --arbitrary-flag. Si manage.sh pasa los argumentos sin comprobarlos a subcomandos privilegiados, tendrá un problema.
Aplique una defensa en profundidad: valide los argumentos dentro del script privilegiado y también en 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."Elevación temporal de privilegios con trampa de limpieza
Cuando un script debe mantener brevemente un archivo, una credencial o un recurso privilegiado, use trap de Bash para garantizar que la limpieza se realice incluso en caso de error o de una señal. Esto evita la filtración de privilegios; por ejemplo, que quede atrás un binario setuid temporal o un socket propiedad de root si el script se bloquea.
El patrón siguiente crea un archivo temporal como root, lo utiliza y después lo elimina; un trap en EXIT lo garantiza:
#!/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."Auditoría y registro de acciones privilegiadas
El principio de mínimo privilegio es más fácil de aplicar cuando cada evento de elevación se registra con contexto: quién ejecutó qué, cuándo y por qué. Combine dos capas:
- El propio sudo:
/var/log/auth.log(Debian/Ubuntu) o/var/log/secure(RHEL) captura automáticamente cada invocación de sudo - Registro de auditoría a nivel de script: escriba una entrada estructurada al inicio de cada función privilegiada para que la intención quede registrada junto al registro del sistema
El uso de una función sencilla para registros estructurados mantiene los historiales de auditoría coherentes y facilita su búsqueda con 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"Comprobación de conocimientos: alcance de sudo
Compruebe su comprensión de la ejecución con mínimo privilegio en scripts de Bash.
Repaso: ejecución con mínimo privilegio y disciplina con sudo
En esta lección ha aprendido las técnicas para ejecutar scripts de Bash con el mínimo privilegio necesario en cada paso. Este es un resumen conciso de los principios clave:
- Verifique con
$EUID: finalice rápidamente si el script se ejecuta con una identidad incorrecta, tanto si debe rechazar root como si debe exigirlo - Limite
sudoa comandos individuales: nunca eleve un script completo; apliquesudoúnicamente a las líneas que realmente lo necesiten - Escriba reglas de sudoers estrictas: especifique rutas absolutas completas y argumentos literales; evite los comodines y
ALL - Quite privilegios con
runuserosudo -u: cuando un script iniciado como root deba delegar la ejecución a un usuario sin privilegios, use la herramienta adecuada en lugar de ejecutarlo todo como root - Use rutas absolutas: nunca dependa de
$PATHdentro de código privilegiado; escriba explícitamente las rutas de los binarios para evitar su secuestro - Valide los argumentos dentro de los scripts privilegiados: las reglas de sudoers son la primera línea de defensa, no la única; use listas de permitidos
- Use trampas y realice la limpieza: use
trap cleanup EXITpara garantizar que los recursos privilegiados temporales se destruyan incluso en caso de error - Registre cada elevación: las entradas de auditoría estructuradas, combinadas con el syslog integrado de sudo, proporcionan trazabilidad de cada acción privilegiada
Aplicadas de forma coherente, estas prácticas reducen la superficie de ataque de su automatización, pasando de root-en-todo-momento a root-solo-cuando-sea-demostrablemente-necesario, la característica distintiva de un Bash reforzado y preparado para producción.
Preguntas frecuentes
¿La lección «Ejecución con mínimos privilegios y disciplina con sudo» es gratis?
Sí — el texto completo de «Ejecución con mínimos privilegios y disciplina con sudo» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.
¿Qué aprenderé en «Ejecución con mínimos privilegios y disciplina con sudo»?
Reduzca privilegios, limite estrictamente las reglas de sudo y valide el UID efectivo antes de realizar operaciones arriesgadas. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar DevOps Bootcamp?
No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Ejecución con mínimos privilegios y disciplina con sudo»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?
Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Prevención de inyección de comandos y argumentos
- Gestión segura de secretos e higiene del entorno
- Ejecución con mínimos privilegios y disciplina con sudo
- Análisis estático y auditoría con ShellCheck