0Pricing
DevOps Bootcamp · Lección

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

Abandono 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 comando
  • runuser -u username -- command args: opción preferida en Linux para cambiar de usuario dentro de scripts propiedad de root; es más limpia que su

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 += PATH o env_keep += LD_*
  • Use sudo -E solo 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.sh

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

  1. El propio sudo: /var/log/auth.log (Debian/Ubuntu) o /var/log/secure (RHEL) captura automáticamente cada invocación de sudo
  2. 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 sudo a comandos individuales: nunca eleve un script completo; aplique sudo ú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 runuser o sudo -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 $PATH dentro 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 EXIT para 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

  1. Prevención de inyección de comandos y argumentos
  2. Gestión segura de secretos e higiene del entorno
  3. Ejecución con mínimos privilegios y disciplina con sudo
  4. Análisis estático y auditoría con ShellCheck
← Volver a DevOps Bootcamp