0Pricing
DevOps Bootcamp · Lección

Automatización del aprovisionamiento de usuarios y grupos

Cree, modifique y audite cuentas en bloque mediante useradd, chage y la gestión de fragmentos de sudoers.

Automatización del aprovisionamiento de usuarios y grupos es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 1 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é automatizar el aprovisionamiento de usuarios?

Gestionar los usuarios uno a uno con useradd funciona bien para un puñado de cuentas, pero los entornos empresariales incorporan habitualmente decenas o cientos de usuarios a la vez. Los comandos manuales son propensos a errores, incoherentes y difíciles de auditar.

Los scripts de Bash le permiten:

  • Crear usuarios con configuraciones estandarizadas (shell, directorio personal y política de contraseñas) en cada ocasión
  • Leer un archivo CSV o de texto con las nuevas incorporaciones y aprovisionarlas en una sola ejecución
  • Registrar cada acción para disponer de un rastro de auditoría con fines de cumplimiento
  • Integrarse con pipelines de gestión de configuración (Ansible, Chef, Jenkins)

En esta lección se explica cómo crear desde cero un script de aprovisionamiento de usuarios preparado para producción, incluyendo useradd, chage, usermod, la gestión de grupos, los archivos drop-in de sudoers y la auditoría posterior a la ejecución.

Lectura de una lista masiva de usuarios

El formato de entrada habitual para el aprovisionamiento masivo es un archivo de texto delimitado: un registro por línea. Un CSV típico podría tener este aspecto:

username,full_name,group,shell
alice,Alice Smith,developers,/bin/bash
bob,Bob Jones,ops,/bin/zsh

Use IFS y read dentro de un bucle while para analizarlo de forma segura. Omitir la línea de encabezado con tail -n +2 mantiene la lógica clara.

Prácticas defensivas clave:

  • Elimine los espacios en blanco iniciales y finales de cada campo
  • Omita las líneas vacías y las líneas de comentarios que comiencen por #
  • Compruebe que los campos obligatorios no estén vacíos antes de llamar a cualquier comando del sistema
#!/usr/bin/env bash
# parse_users.sh — safely read a CSV of users
set -euo pipefail

USER_FILE="${1:-users.csv}"

[[ -f "$USER_FILE" ]] || { echo "ERROR: $USER_FILE not found"; exit 1; }

tail -n +2 "$USER_FILE" | while IFS=',' read -r username full_name group shell; do
  # trim whitespace
  username="${username// /}"
  [[ -z "$username" || "$username" == \#* ]] && continue

  echo "Parsed -> user=$username group=$group shell=$shell"
done

Creación de usuarios con useradd

useradd es la utilidad de bajo nivel que escribe en /etc/passwd, /etc/shadow y /etc/group. Las opciones más importantes para crear scripts son:

  • -m — crear el directorio personal
  • -s — establecer el shell de inicio de sesión
  • -c — campo de comentario GECOS (nombre completo)
  • -G — grupos suplementarios (separados por comas)
  • -e — fecha de caducidad de la cuenta (YYYY-MM-DD)

Compruebe siempre si el usuario ya existe con id antes de llamar a useradd; ejecutarlo sobre un usuario existente devuelve el código de salida 9 y muestra un error que podría saturar los registros.

Nota: useradd requiere privilegios de root. Incluya una comprobación de privilegios al principio del script.

#!/usr/bin/env bash
# create_user.sh — idempotent single-user creation
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USERNAME="$1"
FULL_NAME="${2:-}"
GROUP="${3:-staff}"
SHELL="${4:-/bin/bash}"

if id "$USERNAME" &>/dev/null; then
  echo "[SKIP] User $USERNAME already exists"
else
  useradd \
    --create-home \
    --shell "$SHELL" \
    --comment "$FULL_NAME" \
    --groups "$GROUP" \
    "$USERNAME"
  echo "[OK] Created $USERNAME"
fi

Configuración segura de las contraseñas iniciales

No incluya nunca contraseñas directamente en los scripts. Dos métodos seguros para el aprovisionamiento masivo son:

  • Generar una contraseña inicial aleatoria con openssl rand o /dev/urandom, mostrarla una sola vez y obligar al usuario a cambiarla en el primer inicio de sesión
  • Establecer una contraseña previamente cifrada mediante usermod -p con un hash SHA-512, para que el texto plano nunca aparezca en la lista de procesos

chpasswd es la herramienta recomendada para establecer contraseñas mediante scripts: lee pares username:password de stdin, por lo que la contraseña nunca aparece en los argumentos de la línea de comandos (visibles mediante ps).

Después de establecer la contraseña, use chage -d 0 para obligar a restablecerla en el siguiente inicio de sesión.

#!/usr/bin/env bash
# set_temp_password.sh
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USERNAME="$1"

# Generate a 16-char random password (alphanumeric only)
TMP_PASS=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)

# Set password via chpasswd (password never in argv)
echo "${USERNAME}:${TMP_PASS}" | chpasswd

# Force password change on next login
chage -d 0 "$USERNAME"

echo "[OK] Temporary password for $USERNAME: $TMP_PASS"
echo "[OK] User must change password on first login"

Gestión de la caducidad de contraseñas con chage

chage (change age) controla la política de caducidad de contraseñas almacenada en /etc/shadow. Las políticas de seguridad empresariales suelen exigir:

  • Antigüedad máxima de la contraseña (por ejemplo, 90 días)
  • Número mínimo de días antes de poder volver a cambiar una contraseña
  • Periodo de aviso previo a la caducidad
  • Bloqueo por inactividad de la cuenta tras el último uso de la contraseña

Opciones principales de chage:

  • -M <days> — antigüedad máxima de la contraseña
  • -m <days> — antigüedad mínima de la contraseña
  • -W <days> — días de aviso antes de la caducidad
  • -I <days> — días de inactividad antes de bloquear la cuenta
  • -E <date> — caducidad absoluta de la cuenta
  • -l — mostrar la configuración actual de un usuario
#!/usr/bin/env bash
# apply_password_policy.sh — enforce org-wide ageing policy
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

# Policy constants
MAX_AGE=90
MIN_AGE=1
WARN_DAYS=14
INACTIVE_DAYS=30

apply_policy() {
  local user="$1"
  chage \
    -M "$MAX_AGE" \
    -m "$MIN_AGE" \
    -W "$WARN_DAYS" \
    -I "$INACTIVE_DAYS" \
    "$user"
  echo "[OK] Policy applied to $user"
}

# Apply to all non-system users (UID >= 1000)
awk -F: '$3 >= 1000 && $3 < 65534 { print $1 }' /etc/passwd | while read -r user; do
  apply_policy "$user"
done

Gestión masiva de grupos

Los grupos son el mecanismo principal para controlar el acceso a los recursos. Un script de aprovisionamiento debe asegurarse de que los grupos necesarios existan antes de añadir usuarios a ellos; useradd -G nonexistent fallará.

Use groupadd de forma idempotente comprobando el código de salida: devuelve 9 si el grupo ya existe. La expresión getent group <name> es una alternativa portable y legible a buscar con grep en /etc/group.

gpasswd -a añade un usuario a un grupo sin sustituir sus membresías existentes (a diferencia de usermod -G, que reemplaza la lista de grupos suplementarios).

#!/usr/bin/env bash
# ensure_groups.sh — create groups if missing, then add users
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

REQUIRED_GROUPS=(developers ops security auditors)

for grp in "${REQUIRED_GROUPS[@]}"; do
  if getent group "$grp" &>/dev/null; then
    echo "[SKIP] Group $grp already exists"
  else
    groupadd "$grp"
    echo "[OK] Created group $grp"
  fi
done

# Safely add a user to a group (append, don't replace)
add_to_group() {
  local user="$1" group="$2"
  gpasswd -a "$user" "$group" 2>/dev/null && echo "[OK] $user -> $group"
}

Script completo de aprovisionamiento masivo

Al combinarlo todo, un solo script lee un CSV, crea usuarios y grupos, establece la política de contraseñas, registra cada acción y gestiona los errores correctamente sin detener todo el lote.

Decisiones de diseño importantes del siguiente script:

  • Un LOG_FILE con marcas de tiempo captura todas las operaciones para su auditoría
  • Los errores de usuarios individuales se registran, pero no interrumpen el bucle (|| log_error)
  • El script es idempotente: se puede volver a ejecutar de forma segura después de fallos parciales
  • Toda la salida se dirige tanto al terminal como al archivo de registro mediante tee
#!/usr/bin/env bash
# bulk_provision.sh — production user provisioning
set -uo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USER_FILE="${1:-users.csv}"
LOG_FILE="/var/log/user_provision_$(date +%F).log"

log()  { echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"; }
err()  { log "ERROR: $*"; }

log "=== Provisioning started from $USER_FILE ==="

tail -n +2 "$USER_FILE" | while IFS=',' read -r username fullname group shell; do
  username="${username// /}"
  [[ -z "$username" || "$username" == \#* ]] && continue
  group="${group:-staff}"
  shell="${shell:-/bin/bash}"

  # Ensure group exists
  getent group "$group" &>/dev/null || groupadd "$group"

  # Create user idempotently
  if id "$username" &>/dev/null; then
    log "[SKIP] $username exists"
  else
    useradd -m -s "$shell" -c "$fullname" -G "$group" "$username" || { err "useradd failed for $username"; continue; }
    TMP="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 14)"
    echo "${username}:${TMP}" | chpasswd
    chage -M 90 -m 1 -W 14 -I 30 -d 0 "$username"
    log "[OK] $username created (group=$group) tmp_pass=$TMP"
  fi
done

log "=== Provisioning complete ==="

Gestión de fragmentos drop-in de sudoers

Editar directamente /etc/sudoers es peligroso: un error de sintaxis puede dejar a todo el mundo sin acceso a sudo. El método seguro consiste en utilizar archivos drop-in en /etc/sudoers.d/, validando cada uno con visudo -c -f antes de instalarlo.

Prácticas recomendadas para los fragmentos de sudoers:

  • Asigne a los archivos el nombre del equipo o rol cuyos permisos conceden (por ejemplo, 10-developers, 20-ops)
  • Use reglas basadas en grupos (%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl) en lugar de líneas para usuarios individuales
  • Establezca siempre los permisos en 0440 y el propietario en root:root
  • Valide con visudo -c; devuelve un código distinto de cero ante cualquier error de sintaxis
#!/usr/bin/env bash
# write_sudoers_fragment.sh
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

FRAGMENT_NAME="${1:-10-developers}"
SUDOERS_DIR="/etc/sudoers.d"
TMP_FILE="$(mktemp)"

# Write the fragment to a temp file first
cat > "$TMP_FILE" << 'EOF'
# Developers: restart services and view journals without full root
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *, /usr/bin/journalctl
%ops        ALL=(ALL) NOPASSWD: ALL
EOF

# Validate BEFORE installing
if visudo -c -f "$TMP_FILE"; then
  install -m 0440 -o root -g root "$TMP_FILE" "${SUDOERS_DIR}/${FRAGMENT_NAME}"
  echo "[OK] Installed ${SUDOERS_DIR}/${FRAGMENT_NAME}"
else
  echo "[ERROR] sudoers syntax check failed — fragment NOT installed"
  rm -f "$TMP_FILE"
  exit 1
fi
rm -f "$TMP_FILE"

Auditoría de cuentas existentes

Después del aprovisionamiento (y de forma periódica), debe auditar la base de datos de usuarios para detectar anomalías:

  • Cuentas con UID 0: cualquier cuenta con UID 0 distinta de root constituye un hallazgo de seguridad crítico
  • Cuentas sin contraseña: entradas con un campo de contraseña vacío o con ! en /etc/shadow
  • Cuentas caducadas que siguen activas: la salida de chage -l se puede analizar de forma masiva
  • Usuarios con shell pero sin directorio personal: una configuración incorrecta que impide iniciar sesión

Generar un informe estructurado y enviarlo por correo al equipo de seguridad es sencillo con mail o añadiéndolo a una ruta de registros supervisada.

#!/usr/bin/env bash
# audit_users.sh — produce a security-relevant user report
set -uo pipefail

REPORT="/var/log/user_audit_$(date +%F).txt"

echo "=== User Audit Report $(date) ===" > "$REPORT"

echo "" >> "$REPORT"
echo "--- Accounts with UID 0 (should be root only) ---" >> "$REPORT"
awk -F: '$3 == 0 { print $1 }' /etc/passwd >> "$REPORT"

echo "" >> "$REPORT"
echo "--- Accounts with empty password field ---" >> "$REPORT"
awk -F: '($2 == "" || $2 == "!") && $3 >= 1000 { print $1 }' /etc/shadow 2>/dev/null >> "$REPORT" || echo "  (requires root)" >> "$REPORT"

echo "" >> "$REPORT"
echo "--- Normal users (UID 1000-60000) ---" >> "$REPORT"
awk -F: '$3 >= 1000 && $3 < 60000 { printf "%-20s uid=%-6s shell=%s\n", $1, $3, $7 }' /etc/passwd >> "$REPORT"

cat "$REPORT"
echo "Report saved to $REPORT"

Bloqueo, desbloqueo y eliminación de cuentas

La baja de usuarios es tan importante como su alta. Cuando un usuario abandona la organización, la secuencia correcta es:

  1. Bloquear la cuenta inmediatamente (usermod -L): antepone ! al hash de la contraseña en shadow, lo que impide iniciar sesión sin eliminar los datos
  2. Revocar sudo: elimine su fragmento de sudoers si existe
  3. Transferir la propiedad de sus archivos a un responsable o a una cuenta de archivo
  4. Archivar el directorio personal como un tarball antes de eliminarlo
  5. Eliminar con userdel -r: elimina el directorio personal y la cola de correo

usermod -U desbloquea una cuenta (elimina el prefijo !), lo que resulta útil para una suspensión temporal.

#!/usr/bin/env bash
# offboard_user.sh — lock, archive, then optionally delete
set -euo pipefail

[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }

USERNAME="$1"
ARCHIVE_DIR="/srv/archived-homes"
mkdir -p "$ARCHIVE_DIR"

# 1. Lock account
usermod -L "$USERNAME"
echo "[OK] Account $USERNAME locked"

# 2. Remove sudoers fragment if present
SUDOERS_FILE="/etc/sudoers.d/${USERNAME}"
[[ -f "$SUDOERS_FILE" ]] && rm -f "$SUDOERS_FILE" && echo "[OK] Removed sudoers fragment"

# 3. Archive home directory
HOME_DIR="$(getent passwd "$USERNAME" | cut -d: -f6)"
if [[ -d "$HOME_DIR" ]]; then
  tar -czf "${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz" -C "$(dirname "$HOME_DIR")" "$(basename "$HOME_DIR")"
  echo "[OK] Home archived to ${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz"
fi

echo "[NOTICE] Review archive, then run: userdel -r $USERNAME"

Prueba del script con un modo de simulación

Los scripts de aprovisionamiento para producción deben poder probarse sin efectos secundarios. Implemente un modo de simulación mediante un indicador DRY_RUN que sustituya todos los comandos que modifican el sistema por stubs de echo.

El patrón es sencillo: defina una función auxiliar run() que ejecute o muestre el comando según el indicador. Este enfoque permite:

  • Ejecutar todas las rutas de código durante las pruebas
  • Mostrar exactamente lo que ocurriría en una ejecución real
  • Validar la lógica en los pipelines de CI sin acceso root

Combine el modo de simulación con un prefijo específico para usuarios de prueba (por ejemplo, test_) que facilite la limpieza posterior a las pruebas de integración.

#!/usr/bin/env bash
# provision_with_dryrun.sh
set -euo pipefail

DRY_RUN="${DRY_RUN:-false}"

# Wrapper: execute or echo
run() {
  if [[ "$DRY_RUN" == "true" ]]; then
    echo "[DRY-RUN] $*"
  else
    "$@"
  fi
}

create_user() {
  local user="$1" group="$2"
  if id "$user" &>/dev/null; then
    echo "[SKIP] $user exists"
    return
  fi
  run useradd -m -s /bin/bash -G "$group" "$user"
  run chage -M 90 -m 1 -W 14 -d 0 "$user"
  echo "[OK] $user provisioned (dry=$DRY_RUN)"
}

# Test run
DRY_RUN=true create_user testuser developers
echo "---"
create_user realuser developers 2>/dev/null || true

¿Qué comando debe usar para añadir un usuario a un grupo suplementario sin eliminar sus membresías actuales?

En un script de aprovisionamiento masivo necesita asignar un usuario existente al grupo auditors. El usuario ya pertenece a developers y staff. ¿Qué comando conserva todas las membresías actuales y añade la nueva?

Repaso de la lección: automatización del aprovisionamiento de usuarios y grupos

En esta lección ha creado un kit completo de aprovisionamiento de usuarios preparado para producción. Estas son las conclusiones principales:

  • Analice la entrada de forma defensiva: use bucles con IFS/read, omita las líneas vacías y de comentarios, y valide los campos antes de cualquier llamada al sistema
  • Aspectos esenciales de useradd: use siempre -m (directorio personal), -s (shell), -c (comentario) y -G (grupos); compruebe primero la existencia con id para garantizar la idempotencia
  • Contraseñas: establézcalas mediante stdin de chpasswd para mantener el texto plano fuera de los argumentos del proceso; fuerce el restablecimiento en el primer inicio de sesión con chage -d 0
  • chage para aplicar políticas: estandarice la antigüedad máxima (-M), los días de aviso (-W) y el bloqueo por inactividad (-I) en todas las cuentas que no sean del sistema
  • Membresía de grupos: use gpasswd -a o usermod -aG (con la opción -a) para añadir membresías en lugar de sustituirlas
  • Archivos drop-in de sudoers: escriba en /etc/sudoers.d/, valide con visudo -c -f antes de instalarlos y establezca los permisos 0440 root:root
  • Baja de usuarios: bloquee (usermod -L), archive el directorio personal y después elimine; no omita nunca el archivado
  • Modo de simulación: incluya los comandos que modifican el sistema en una función auxiliar run() para que los pipelines puedan verificar la lógica sin efectos secundarios que requieran root

La combinación de estos patrones le proporciona una capa de automatización repetible, auditable y segura para gestionar identidades Linux a cualquier escala.

Preguntas frecuentes

¿La lección «Automatización del aprovisionamiento de usuarios y grupos» es gratis?

Sí — el texto completo de «Automatización del aprovisionamiento de usuarios y grupos» 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 «Automatización del aprovisionamiento de usuarios y grupos»?

Cree, modifique y audite cuentas en bloque mediante useradd, chage y la gestión de fragmentos de sudoers. 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 1 de 4.

¿Cuánto tiempo toma la lección «Automatización del aprovisionamiento de usuarios y grupos»?

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. Automatización del aprovisionamiento de usuarios y grupos
  2. Control de servicios systemd y escritura de archivos unit
  3. Automatización de discos, sistemas de archivos y montajes
  4. Creación de scripts de comprobación y alerta del estado del sistema
← Volver a DevOps Bootcamp