Scripting en Bash: La Guía Completa para Automatizar Tareas en DevOps

En el ecosistema DevOps actual, donde la velocidad y confiabilidad son fundamentales, el scripting en Bash se mantiene como una herramienta esencial para la automatización. A pesar del surgimiento de lenguajes modernos como Python y Go, Bash sigue siendo el estándar de facto para operaciones del sistema, orquestación de herramientas y automatización de pipelines en entornos Unix/Linux.
Esta guía completa te llevará desde los fundamentos hasta técnicas avanzadas, con ejemplos prácticos de producción que podrás implementar inmediatamente en tu flujo de trabajo DevOps.
¿Por Qué Bash Sigue Siendo Relevante en DevOps Moderno?
Ventajas Clave de Bash en DevOps
1. Ubicuidad Universal
- Instalado por defecto en prácticamente todos los sistemas Unix/Linux
- Disponible en contenedores Docker sin dependencias adicionales
- Soporte nativo en pipelines CI/CD (GitHub Actions, GitLab CI, Jenkins)
2. Integración Directa con el Sistema
- Acceso directo a comandos del sistema operativo
- Manipulación eficiente de archivos y procesos
- Control granular sobre permisos y recursos
3. Glue Code Perfecto
- Excelente para orquestar herramientas heterogéneas
- Combinación fluida de utilidades de línea de comandos
- Interfaz natural con APIs REST mediante curl
4. Performance y Overhead Mínimo
- Tiempo de inicio ultra rápido comparado con lenguajes interpretados
- Consumo mínimo de memoria
- Ideal para scripts ligeros y operaciones frecuentes
Casos de Uso Críticos en DevOps
| Área | Aplicaciones de Bash | Ejemplos Prácticos |
|---|---|---|
| CI/CD | Scripts de build, testing, deployment | Pipeline de compilación, validación de artefactos |
| Infraestructura | Provisioning, configuración, monitoring | Inicialización de servidores, health checks |
| Operaciones | Backup, recovery, maintenance | Rotación de logs, limpieza de recursos |
| Seguridad | Auditoría, compliance, hardening | Escaneo de vulnerabilidades, aplicación de políticas |
| Monitoreo | Métricas, alertas, troubleshooting | Recolección de datos, notificaciones automatizadas |
Fundamentos Avanzados de Bash Scripting
Estructura Profesional de Scripts
Un script de Bash bien estructurado debe seguir estas convenciones:
#!/bin/bash
#
# deploy-app.sh - Deployment automatizado de aplicaciones
#
# Descripción: Script para desplegar aplicaciones con validación y rollback
# Autor: DevOps Team
# Versión: 2.1.0
# Última actualización: 2025-04-27
#
# Uso: ./deploy-app.sh [opciones] <environment> <version>
#
# Requiere: docker, kubectl, jq
#
# Configuración estricta para prevenir errores
set -euo pipefail # Exit on error, undefined vars, pipe failures
IFS=$'\n\t' # Secure Internal Field Separator
# Variables de configuración
readonly SCRIPT_NAME="$(basename "${0}")"
readonly SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)"
readonly SCRIPT_VERSION="2.1.0"
readonly LOG_FILE="/var/log/deployment.log"
# Colores para output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Función de limpieza
cleanup() {
local exit_code=$?
log "INFO" "Ejecutando limpieza..."
# Limpiar archivos temporales, restaurar estado, etc.
exit $exit_code
}
# Configurar trap para limpieza automática
trap cleanup EXIT INT TERM
# Resto del script...
Manejo Avanzado de Variables y Parámetros
#!/bin/bash
# Configuración con valores por defecto y validación
declare -r DEFAULT_TIMEOUT=300
declare -r DEFAULT_RETRIES=3
declare -r DEFAULT_ENVIRONMENT="staging"
# Procesamiento de argumentos con getopts
usage() {
cat <EOF
Uso: $0 [OPCIONES] <aplicación> <versión>
OPCIONES:
-e, --env ENVIRONMENT Entorno de despliegue (dev|staging|prod)
-t, --timeout SECONDS Timeout para operaciones (default: $DEFAULT_TIMEOUT)
-r, --retries NUMBER Número de reintentos (default: $DEFAULT_RETRIES)
-v, --verbose Modo verboso
-d, --dry-run Simular ejecución sin cambios
-h, --help Mostrar esta ayuda
EJEMPLOS:
$0 -e prod myapp v1.2.3
$0 --dry-run --verbose -t 600 myapp latest
EOF
}
# Variables con configuración por defecto
ENVIRONMENT="$DEFAULT_ENVIRONMENT"
TIMEOUT="$DEFAULT_TIMEOUT"
RETRIES="$DEFAULT_RETRIES"
VERBOSE=false
DRY_RUN=false
# Procesamiento de argumentos
while [[ $# -gt 0 ]]; do
case $1 in
-e|--env)
ENVIRONMENT="$2"
shift 2
;;
-t|--timeout)
TIMEOUT="$2"
shift 2
;;
-r|--retries)
RETRIES="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
usage
exit 0
;;
-*)
echo "Opción desconocida: $1" >&2
usage >&2
exit 1
;;
*)
break
;;
esac
done
# Validación de argumentos obligatorios
if [[ $# -lt 2 ]]; then
echo "Error: Se requieren aplicación y versión" >&2
usage >&2
exit 1
fi
readonly APP_NAME="$1"
readonly APP_VERSION="$2"
# Validación de entorno
case "$ENVIRONMENT" in
dev|staging|prod)
log "INFO" "Desplegando en entorno: $ENVIRONMENT"
;;
*)
log "ERROR" "Entorno inválido: $ENVIRONMENT"
exit 1
;;
esac
Sistema de Logging Robusto
#!/bin/bash
# Sistema de logging con diferentes niveles
declare -r LOG_LEVELS=("DEBUG" "INFO" "WARN" "ERROR" "FATAL")
declare -i LOG_LEVEL=1 # Por defecto INFO (1)
# Función de logging mejorada
log() {
local level="$1"
local message="$2"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local caller="${BASH_SOURCE[2]##*/}:${BASH_LINENO[1]}"
# Determinar nivel numérico
local level_num
case "$level" in
DEBUG) level_num=0 ;;
INFO) level_num=1 ;;
WARN) level_num=2 ;;
ERROR) level_num=3 ;;
FATAL) level_num=4 ;;
*) level_num=1 ;;
esac
# Solo mostrar si el nivel es suficiente
if [[ $level_num -ge $LOG_LEVEL ]]; then
# Color según nivel
local color=""
case "$level" in
DEBUG) color="$BLUE" ;;
INFO) color="$GREEN" ;;
WARN) color="$YELLOW" ;;
ERROR|FATAL) color="$RED" ;;
esac
# Formato de salida
printf "${color}[%s] %-5s %s: %s${NC}\n" \
"$timestamp" "$level" "$caller" "$message" | tee -a "$LOG_FILE"
# Para errores fatales, enviar alertas
if [[ "$level" == "FATAL" ]]; then
send_alert "FATAL" "$message"
exit 1
fi
fi
}
# Función para enviar alertas
send_alert() {
local level="$1"
local message="$2"
local hostname
hostname=$(hostname)
# Slack webhook (si está configurado)
if [[ -n "${SLACK_WEBHOOK:-}" ]]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"[$level] $hostname: $message\"}" \
"$SLACK_WEBHOOK" &>/dev/null &
fi
# Email (si está configurado)
if [[ -n "${ALERT_EMAIL:-}" ]] && command -v mail &>/dev/null; then
echo "$message" | mail -s "[$level] Alert from $hostname" "$ALERT_EMAIL" &
fi
# PagerDuty, OpsGenie, etc. según configuración
}
Automatización de Pipelines CI/CD
Script de Build Completo con Validaciones
#!/bin/bash
#
# build-pipeline.sh - Pipeline de build completo con validaciones
#
set -euo pipefail
# Configuración
readonly BUILD_DIR="$(mktemp -d)"
readonly ARTIFACT_DIR="/artifacts"
readonly REGISTRY="registry.company.com"
readonly SONAR_PROJECT_KEY="myapp"
# Función principal de build
main() {
log "INFO" "Iniciando pipeline de build para $APP_NAME:$APP_VERSION"
validate_environment
setup_workspace
run_security_checks
run_tests
build_application
build_container_image
run_security_scan
publish_artifacts
update_documentation
cleanup_workspace
log "INFO" "Pipeline de build completado exitosamente"
}
# Validación del entorno de build
validate_environment() {
log "INFO" "Validando entorno de build"
# Verificar herramientas requeridas
local tools=("docker" "git" "node" "npm" "sonar-scanner" "trivy" "cosign")
for tool in "${tools[@]}"; do
if ! command -v "$tool" &>/dev/null; then
log "FATAL" "Herramienta requerida no encontrada: $tool"
fi
done
# Verificar variables de entorno críticas
local env_vars=("REGISTRY_USERNAME" "REGISTRY_PASSWORD" "SONAR_TOKEN")
for var in "${env_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
log "FATAL" "Variable de entorno requerida: $var"
fi
done
# Verificar espacio en disco (mínimo 5GB)
local available_space
available_space=$(df "$PWD" | awk 'NR==2 {print $4}')
if [[ $available_space -lt 5242880 ]]; then # 5GB en KB
log "FATAL" "Espacio insuficiente en disco: ${available_space}KB disponible"
fi
log "INFO" "Entorno validado correctamente"
}
# Configuración del workspace
setup_workspace() {
log "INFO" "Configurando workspace en $BUILD_DIR"
# Limpiar workspace previo si existe
if [[ -d "$BUILD_DIR" ]]; then
rm -rf "$BUILD_DIR"
fi
mkdir -p "$BUILD_DIR" "$ARTIFACT_DIR"
cd "$BUILD_DIR"
# Clonar repositorio con shallow clone para velocidad
git clone --depth 1 --branch "$GIT_BRANCH" "$GIT_REPO_URL" .
# Verificar integridad del código
if [[ -f ".git-integrity" ]]; then
local expected_hash
expected_hash=$(cat .git-integrity)
local actual_hash
actual_hash=$(git rev-parse HEAD)
if [[ "$expected_hash" != "$actual_hash" ]]; then
log "WARN" "Hash de integridad no coincide. Esperado: $expected_hash, Actual: $actual_hash"
fi
fi
log "INFO" "Workspace configurado en $BUILD_DIR"
}
# Verificaciones de seguridad del código
run_security_checks() {
log "INFO" "Ejecutando verificaciones de seguridad"
# Escaneo de secretos con gitleaks
if command -v gitleaks &>/dev/null; then
log "INFO" "Escaneando secretos con gitleaks"
if ! gitleaks detect --source . --verbose; then
log "FATAL" "Se encontraron secretos en el código"
fi
fi
# Análisis de dependencias vulnerables
if [[ -f "package.json" ]]; then
log "INFO" "Analizando vulnerabilidades en dependencias npm"
npm audit --audit-level=moderate
# Verificar licencias
if command -v license-checker &>/dev/null; then
license-checker --onlyAllow 'MIT;BSD;Apache-2.0;ISC' --production
fi
fi
# SAST con SonarQube
if [[ -n "${SONAR_TOKEN:-}" ]]; then
log "INFO" "Ejecutando análisis SAST con SonarQube"
sonar-scanner \
-Dsonar.projectKey="$SONAR_PROJECT_KEY" \
-Dsonar.sources=. \
-Dsonar.host.url="$SONAR_HOST_URL" \
-Dsonar.login="$SONAR_TOKEN"
fi
log "INFO" "Verificaciones de seguridad completadas"
}
# Ejecución de tests
run_tests() {
log "INFO" "Ejecutando suite de tests"
# Tests unitarios
log "INFO" "Ejecutando tests unitarios"
npm run test:unit -- --coverage --ci
# Tests de integración
if [[ "$ENVIRONMENT" != "dev" ]]; then
log "INFO" "Ejecutando tests de integración"
npm run test:integration
fi
# Tests de carga (solo en staging/prod)
if [[ "$ENVIRONMENT" =~ ^(staging|prod)$ ]]; then
log "INFO" "Ejecutando tests de carga"
npm run test:load
fi
# Verificar cobertura mínima
local coverage
coverage=$(grep -o '"pct":[0-9.]*' coverage/coverage-summary.json | head -1 | cut -d: -f2)
if (( $(echo "$coverage 80" | bc -l) )); then
log "FATAL" "Cobertura insuficiente: ${coverage}% (mínimo: 80%)"
fi
log "INFO" "Tests completados exitosamente. Cobertura: ${coverage}%"
}
# Build de la aplicación
build_application() {
log "INFO" "Construyendo aplicación"
# Instalar dependencias
npm ci --production
# Build optimizado
NODE_ENV=production npm run build
# Verificar que el build fue exitoso
if [[ ! -d "dist" ]] || [[ ! "$(ls -A dist)" ]]; then
log "FATAL" "El directorio dist está vacío o no existe"
fi
# Optimizar assets
if command -v terser &>/dev/null; then
find dist -name "*.js" -exec terser {} -o {} \;
fi
log "INFO" "Aplicación construida exitosamente"
}
# Build de imagen de contenedor
build_container_image() {
local image_tag="$REGISTRY/$APP_NAME:$APP_VERSION"
local latest_tag="$REGISTRY/$APP_NAME:latest"
log "INFO" "Construyendo imagen Docker: $image_tag"
# Build multi-stage con cache
docker build \
--target production \
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg VCS_REF="$(git rev-parse --short HEAD)" \
--build-arg VERSION="$APP_VERSION" \
--cache-from "$latest_tag" \
--tag "$image_tag" \
--tag "$latest_tag" \
.
# Verificar que la imagen se creó correctamente
if ! docker images "$image_tag" | grep -q "$APP_VERSION"; then
log "FATAL" "Error al crear la imagen Docker"
fi
# Test rápido de la imagen
log "INFO" "Verificando imagen Docker"
if ! docker run --rm "$image_tag" sh -c 'echo "Image OK"'; then
log "FATAL" "La imagen Docker no ejecuta correctamente"
fi
log "INFO" "Imagen Docker creada: $image_tag"
}
# Escaneo de seguridad de la imagen
run_security_scan() {
local image_tag="$REGISTRY/$APP_NAME:$APP_VERSION"
log "INFO" "Escaneando imagen por vulnerabilidades"
# Trivy para vulnerabilidades
if command -v trivy &>/dev/null; then
if ! trivy image --exit-code 1 --severity HIGH,CRITICAL "$image_tag"; then
log "FATAL" "Se encontraron vulnerabilidades críticas en la imagen"
fi
fi
# Grype como alternativa
if command -v grype &>/dev/null; then
grype "$image_tag" --fail-on high
fi
log "INFO" "Escaneo de seguridad completado"
}
# Publicación de artefactos
publish_artifacts() {
local image_tag="$REGISTRY/$APP_NAME:$APP_VERSION"
log "INFO" "Publicando artefactos"
# Login en registry
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
# Push de imagen
docker push "$image_tag"
# Firmar imagen con cosign (si está configurado)
if command -v cosign &>/dev/null && [[ -n "${COSIGN_PRIVATE_KEY:-}" ]]; then
log "INFO" "Firmando imagen con cosign"
cosign sign --key "$COSIGN_PRIVATE_KEY" "$image_tag"
fi
# Generar SBOM (Software Bill of Materials)
if command -v syft &>/dev/null; then
syft "$image_tag" -o json > "$ARTIFACT_DIR/sbom.json"
fi
# Artefactos adicionales
cp -r dist "$ARTIFACT_DIR/"
cp package.json "$ARTIFACT_DIR/"
log "INFO" "Artefactos publicados exitosamente"
}
# Actualización de documentación
update_documentation() {
log "INFO" "Actualizando documentación"
# Generar changelog automático
if command -v conventional-changelog &>/dev/null; then
conventional-changelog -p angular -i CHANGELOG.md -s
fi
# Actualizar versión en archivos
sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/" package.json
# Commit de cambios de versión
if [[ "$ENVIRONMENT" == "prod" ]]; then
git add package.json CHANGELOG.md
git commit -m "chore: update version to $APP_VERSION"
git tag -a "v$APP_VERSION" -m "Release version $APP_VERSION"
fi
log "INFO" "Documentación actualizada"
}
# Limpieza del workspace
cleanup_workspace() {
log "INFO" "Limpiando workspace"
# Limpiar imágenes Docker temporales
docker image prune -f --filter "until=24h"
# Limpiar build directory
if [[ -d "$BUILD_DIR" ]]; then
rm -rf "$BUILD_DIR"
fi
log "INFO" "Workspace limpio"
}
# Verificar si se ejecuta directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Monitoreo y Observabilidad Avanzada
Sistema de Monitoreo Integral
#!/bin/bash
#
# monitoring-agent.sh - Agente de monitoreo integral para infraestructura
#
set -euo pipefail
# Configuración
readonly METRICS_DIR="/var/metrics"
readonly ALERT_THRESHOLD_CPU=80
readonly ALERT_THRESHOLD_MEMORY=85
readonly ALERT_THRESHOLD_DISK=90
readonly ALERT_THRESHOLD_IOWAIT=30
readonly CHECK_INTERVAL=60
# Servicios críticos a monitorear
readonly CRITICAL_SERVICES=(
"nginx"
"postgresql"
"redis-server"
"elasticsearch"
"prometheus"
"grafana-server"
)
# URLs para health checks
readonly HEALTH_ENDPOINTS=(
"http://localhost/health"
"http://localhost:3000/api/health"
"http://localhost:9200/_cluster/health"
)
# Inicialización
init_monitoring() {
log "INFO" "Inicializando sistema de monitoreo"
mkdir -p "$METRICS_DIR"
# Crear archivos de métricas si no existen
touch "$METRICS_DIR/system.csv"
touch "$METRICS_DIR/services.csv"
touch "$METRICS_DIR/network.csv"
touch "$METRICS_DIR/disk_io.csv"
# Headers para archivos CSV
if [[ ! -s "$METRICS_DIR/system.csv" ]]; then
echo "timestamp,cpu_usage,memory_usage,load_avg,disk_usage,swap_usage,iowait" > "$METRICS_DIR/system.csv"
fi
if [[ ! -s "$METRICS_DIR/services.csv" ]]; then
echo "timestamp,service,status,cpu_percent,memory_mb,connections" > "$METRICS_DIR/services.csv"
fi
log "INFO" "Sistema de monitoreo inicializado"
}
# Recolección de métricas del sistema
collect_system_metrics() {
local timestamp
timestamp=$(date +%s)
# CPU Usage
local cpu_usage
cpu_usage=$(grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$3+$4+$5)} END {print usage}')
# Memory Usage
local memory_info
memory_info=$(free | awk '/^Mem:/ {printf "%.2f %.2f", $3*100/$2, $4*100/$2}')
local memory_usage
memory_usage=$(echo "$memory_info" | cut -d' ' -f1)
local memory_free
memory_free=$(echo "$memory_info" | cut -d' ' -f2)
# Load Average
local load_avg
load_avg=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
# Disk Usage (root filesystem)
local disk_usage
disk_usage=$(df / | awk 'NR==2 {print substr($5, 1, length($5)-1)}')
# Swap Usage
local swap_usage
swap_usage=$(free | awk '/^Swap:/ { if($2 > 0) print $3*100/$2; else print 0}')
# I/O Wait
local iowait
iowait=$(iostat -c 1 2 | awk '/^avg-cpu:/ { getline; print $4 }' | tail -1)
# Guardar métricas
echo "$timestamp,$cpu_usage,$memory_usage,$load_avg,$disk_usage,$swap_usage,$iowait" >> "$METRICS_DIR/system.csv"
# Verificar umbrales y enviar alertas
check_system_thresholds "$cpu_usage" "$memory_usage" "$disk_usage" "$iowait"
# Log métricas actuales
log "DEBUG" "Métricas del sistema - CPU: ${cpu_usage}%, Mem: ${memory_usage}%, Disk: ${disk_usage}%, Load: $load_avg, IOWait: ${iowait}%"
}
# Verificación de umbrales críticos
check_system_thresholds() {
local cpu_usage="$1"
local memory_usage="$2"
local disk_usage="$3"
local iowait="$4"
# CPU
if (( $(echo "$cpu_usage > $ALERT_THRESHOLD_CPU" | bc -l) )); then
send_alert "WARNING" "Alto uso de CPU: ${cpu_usage}% (umbral: ${ALERT_THRESHOLD_CPU}%)"
fi
# Memory
if (( $(echo "$memory_usage > $ALERT_THRESHOLD_MEMORY" | bc -l) )); then
send_alert "WARNING" "Alto uso de memoria: ${memory_usage}% (umbral: ${ALERT_THRESHOLD_MEMORY}%)"
fi
# Disk
if [[ ${disk_usage%.*} -gt $ALERT_THRESHOLD_DISK ]]; then
send_alert "CRITICAL" "Alto uso de disco: ${disk_usage}% (umbral: ${ALERT_THRESHOLD_DISK}%)"
fi
# I/O Wait
if (( $(echo "$iowait > $ALERT_THRESHOLD_IOWAIT" | bc -l) )); then
send_alert "WARNING" "Alto I/O wait: ${iowait}% (umbral: ${ALERT_THRESHOLD_IOWAIT}%)"
fi
}
# Monitoreo de servicios críticos
monitor_services() {
local timestamp
timestamp=$(date +%s)
for service in "${CRITICAL_SERVICES[@]}"; do
local status="DOWN"
local cpu_percent=0
local memory_mb=0
local connections=0
# Verificar estado del servicio
if systemctl is-active --quiet "$service"; then
status="UP"
# Obtener PID del servicio
local pid
pid=$(systemctl show "$service" --property=MainPID --value)
if [[ "$pid" != "0" ]] && [[ -n "$pid" ]]; then
# CPU y Memory del proceso
local proc_stats
proc_stats=$(ps -p "$pid" -o %cpu,%mem --no-headers 2>/dev/null || echo "0.0 0.0")
cpu_percent=$(echo "$proc_stats" | awk '{print $1}')
memory_mb=$(echo "$proc_stats" | awk '{print $2}')
# Convertir memoria de porcentaje a MB
local total_memory_kb
total_memory_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
memory_mb=$(echo "scale=0; $memory_mb * $total_memory_kb / 100 / 1024" | bc)
# Conexiones de red (para servicios de red)
case "$service" in
nginx|apache2)
connections=$(ss -tuln | grep -c ':80\|:443' || echo 0)
;;
postgresql)
connections=$(ss -tuln | grep -c ':5432' || echo 0)
;;
redis-server)
connections=$(ss -tuln | grep -c ':6379' || echo 0)
;;
esac
fi
else
send_alert "CRITICAL" "Servicio crítico caído: $service"
fi
# Guardar métricas del servicio
echo "$timestamp,$service,$status,$cpu_percent,$memory_mb,$connections" >> "$METRICS_DIR/services.csv"
log "DEBUG" "Servicio $service - Estado: $status, CPU: ${cpu_percent}%, Mem: ${memory_mb}MB, Conn: $connections"
done
}
# Health checks de endpoints HTTP
check_health_endpoints() {
local timestamp
timestamp=$(date +%s)
for endpoint in "${HEALTH_ENDPOINTS[@]}"; do
local status_code=0
local response_time=0
local status="DOWN"
# Realizar health check con timeout
local curl_result
curl_result=$(curl -s -w "%{http_code} %{time_total}" -m 10 "$endpoint" 2>/dev/null || echo "000 0")
status_code=$(echo "$curl_result" | awk '{print $(NF-1)}')
response_time=$(echo "$curl_result" | awk '{print $NF}')
if [[ "$status_code" =~ ^2[0-9][0-9]$ ]]; then
status="UP"
else
status="DOWN"
send_alert "WARNING" "Health check falló para $endpoint (HTTP $status_code)"
fi
# Alerta por tiempo de respuesta lento
if (( $(echo "$response_time > 5.0" | bc -l) )); then
send_alert "WARNING" "Respuesta lenta en $endpoint: ${response_time}s"
fi
log "DEBUG" "Health check $endpoint - Status: $status_code, Time: ${response_time}s"
done
}
# Monitoreo de red
monitor_network() {
local timestamp
timestamp=$(date +%s)
# Estadísticas de interfaces de red
local network_stats
network_stats=$(cat /proc/net/dev | grep -E '(eth0|ens|enp)' | head -1)
if [[ -n "$network_stats" ]]; then
local interface
interface=$(echo "$network_stats" | cut -d: -f1 | tr -d ' ')
local rx_bytes
rx_bytes=$(echo "$network_stats" | awk '{print $2}')
local tx_bytes
tx_bytes=$(echo "$network_stats" | awk '{print $10}')
echo "$timestamp,$interface,$rx_bytes,$tx_bytes" >> "$METRICS_DIR/network.csv"
fi
# Verificar conectividad externa
if ! ping -c 1 8.8.8.8 &>/dev/null; then
send_alert "WARNING" "Sin conectividad externa - DNS público no accesible"
fi
}
# Monitoreo de I/O de disco
monitor_disk_io() {
local timestamp
timestamp=$(date +%s)
# Estadísticas de I/O por dispositivo
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+(sd[a-z]|nvme[0-9]n[0-9]) ]]; then
local device
device=$(echo "$line" | awk '{print $3}')
local reads
reads=$(echo "$line" | awk '{print $4}')
local writes
writes=$(echo "$line" | awk '{print $8}')
echo "$timestamp,$device,$reads,$writes" >> "$METRICS_DIR/disk_io.csv"
fi
done /proc/diskstats
}
# Análisis de logs en tiempo real
analyze_logs() {
local error_patterns=(
"ERROR"
"FATAL"
"CRITICAL"
"Out of memory"
"Connection refused"
"Too many open files"
)
# Verificar logs del sistema
for pattern in "${error_patterns[@]}"; do
local recent_errors
recent_errors=$(grep "$pattern" /var/log/syslog | tail -10)
if [[ -n "$recent_errors" ]]; then
send_alert "WARNING" "Errores detectados en syslog: $pattern"
fi
done
# Análisis específico por servicio
if [[ -f "/var/log/nginx/error.log" ]]; then
local nginx_errors
nginx_errors=$(tail -100 /var/log/nginx/error.log | grep -c "error" || echo 0)
if [[ "$nginx_errors" -gt 10 ]]; then
send_alert "WARNING" "Alto número de errores en nginx: $nginx_errors en los últimos 100 logs"
fi
fi
}
# Reporte de métricas para exportar
generate_metrics_report() {
local report_file="$METRICS_DIR/report_$(date +%Y%m%d_%H%M).json"
# Resumen de métricas recientes
local latest_system
latest_system=$(tail -1 "$METRICS_DIR/system.csv" | cut -d, -f2-)
local services_up
services_up=$(grep "$(date +%s)" "$METRICS_DIR/services.csv" | grep -c "UP" || echo 0)
local services_down
services_down=$(grep "$(date +%s)" "$METRICS_DIR/services.csv" | grep -c "DOWN" || echo 0)
# Generar JSON del reporte
cat > "$report_file" <EOF
{
"timestamp": "$(date -Iseconds)",
"hostname": "$(hostname)",
"system": {
"cpu_usage": $(echo "$latest_system" | cut -d, -f1),
"memory_usage": $(echo "$latest_system" | cut -d, -f2),
"disk_usage": $(echo "$latest_system" | cut -d, -f4)
},
"services": {
"up": $services_up,
"down": $services_down
},
"alerts_last_hour": $(grep "$(date -d '1 hour ago' +%s)" /var/log/monitoring.log | wc -l || echo 0)
}
EOF
log "INFO" "Reporte generado: $report_file"
}
# Función principal de monitoreo
main_monitoring_loop() {
log "INFO" "Iniciando bucle principal de monitoreo"
while true; do
collect_system_metrics
monitor_services
check_health_endpoints
monitor_network
monitor_disk_io
analyze_logs
# Generar reporte cada 10 minutos
if (( $(date +%M) % 10 == 0 )); then
generate_metrics_report
fi
log "DEBUG" "Ciclo de monitoreo completado, esperando $CHECK_INTERVAL segundos"
sleep "$CHECK_INTERVAL"
done
}
# Función principal
main() {
log "INFO" "Iniciando agente de monitoreo"
init_monitoring
# Verificar si se ejecuta como daemon
if [[ "${1:-}" == "--daemon" ]]; then
log "INFO" "Ejecutando como daemon"
main_monitoring_loop &
echo $! > /var/run/monitoring-agent.pid
log "INFO" "Daemon iniciado con PID: $(cat /var/run/monitoring-agent.pid)"
else
# Ejecutar una vez
collect_system_metrics
monitor_services
check_health_endpoints
generate_metrics_report
fi
}
# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Automatización de Deployments con Rollback
Sistema de Deployment con Rollback Automático
#!/bin/bash
#
# smart-deploy.sh - Sistema inteligente de deployment con rollback automático
#
set -euo pipefail
# Configuración
readonly DEPLOY_TIMEOUT=900 # 15 minutos
readonly HEALTH_CHECK_TIMEOUT=300 # 5 minutos
readonly ROLLBACK_TIMEOUT=180 # 3 minutos
readonly MIN_HEALTHY_INSTANCES=2
# Estados de deployment
readonly DEPLOY_STATES=("PENDING" "IN_PROGRESS" "VERIFYING" "COMPLETED" "FAILED" "ROLLING_BACK" "ROLLED_BACK")
# Función principal de deployment
deploy_application() {
local environment="$1"
local version="$2"
local strategy="${3:-blue-green}" # blue-green, rolling, canary
log "INFO" "Iniciando deployment de $APP_NAME:$version en $environment con estrategia $strategy"
validate_deployment_preconditions "$environment" "$version"
case "$strategy" in
"blue-green")
deploy_blue_green "$environment" "$version"
;;
"rolling")
deploy_rolling_update "$environment" "$version"
;;
"canary")
deploy_canary "$environment" "$version"
;;
*)
log "FATAL" "Estrategia de deployment desconocida: $strategy"
;;
esac
}
# Validación de precondiciones
validate_deployment_preconditions() {
local environment="$1"
local version="$2"
log "INFO" "Validando precondiciones para deployment"
# Verificar que la imagen existe
if ! docker manifest inspect "$REGISTRY/$APP_NAME:$version" &>/dev/null; then
log "FATAL" "La imagen $REGISTRY/$APP_NAME:$version no existe"
fi
# Verificar conectividad con Kubernetes
if ! kubectl cluster-info &>/dev/null; then
log "FATAL" "No hay conectividad con el cluster de Kubernetes"
fi
# Verificar que el namespace existe
if ! kubectl get namespace "$environment" &>/dev/null; then
log "FATAL" "El namespace '$environment' no existe"
fi
# Verificar recursos disponibles
local node_resources
node_resources=$(kubectl top nodes --no-headers | awk '{sum+=$3} END {print sum}')
if [[ ${node_resources%m} -lt 1000 ]]; then # Menos de 1 CPU disponible
log "WARN" "Recursos limitados en el cluster: ${node_resources}m CPU disponible"
fi
# Verificar que no hay otro deployment en curso
local current_rollout
current_rollout=$(kubectl rollout status deployment/"$APP_NAME" -n "$environment" --timeout=5s 2>&1 || echo "")
if [[ "$current_rollout" == *"Waiting for"* ]]; then
log "FATAL" "Ya hay un deployment en curso para $APP_NAME en $environment"
fi
log "INFO" "Precondiciones validadas correctamente"
}
# Deployment Blue-Green
deploy_blue_green() {
local environment="$1"
local version="$2"
local blue_deployment="$APP_NAME-blue"
local green_deployment="$APP_NAME-green"
log "INFO" "Ejecutando deployment Blue-Green"
# Determinar cuál es el deployment activo actualmente
local active_deployment
active_deployment=$(get_active_deployment "$environment")
local target_deployment
if [[ "$active_deployment" == "$blue_deployment" ]]; then
target_deployment="$green_deployment"
else
target_deployment="$blue_deployment"
fi
log "INFO" "Deployment activo: $active_deployment, Target: $target_deployment"
# Crear/actualizar el target deployment
create_deployment "$environment" "$target_deployment" "$version"
# Esperar a que esté listo
if wait_for_deployment_ready "$environment" "$target_deployment"; then
# Verificar health checks
if verify_application_health "$environment" "$target_deployment"; then
# Cambiar tráfico al nuevo deployment
switch_traffic "$environment" "$target_deployment"
# Verificar estabilidad post-switch
if verify_post_deployment_stability "$environment" "$target_deployment"; then
log "INFO" "Deployment Blue-Green completado exitosamente"
cleanup_old_deployment "$environment" "$active_deployment"
return 0
else
log "ERROR" "Verificación post-deployment falló, iniciando rollback"
rollback_blue_green "$environment" "$active_deployment"
return 1
fi
else
log "ERROR" "Health checks fallaron, iniciando rollback"
cleanup_deployment "$environment" "$target_deployment"
return 1
fi
else
log "ERROR" "Deployment no alcanzó estado ready, iniciando limpieza"
cleanup_deployment "$environment" "$target_deployment"
return 1
fi
}
# Deployment Rolling Update
deploy_rolling_update() {
local environment="$1"
local version="$2"
log "INFO" "Ejecutando Rolling Update"
# Guardar estado actual para rollback
local current_image
current_image=$(kubectl get deployment "$APP_NAME" -n "$environment" -o jsonpath='{.spec.template.spec.containers[0].image}')
# Actualizar imagen
kubectl set image deployment/"$APP_NAME" -n "$environment" "$APP_NAME"="$REGISTRY/$APP_NAME:$version"
# Monitorear el rollout
if monitor_rolling_update "$environment"; then
if verify_application_health "$environment" "$APP_NAME"; then
log "INFO" "Rolling update completado exitosamente"
return 0
else
log "ERROR" "Health checks fallaron post-deployment"
rollback_deployment "$environment" "$current_image"
return 1
fi
else
log "ERROR" "Rolling update falló"
rollback_deployment "$environment" "$current_image"
return 1
fi
}
# Deployment Canary
deploy_canary() {
local environment="$1"
local version="$2"
local canary_percentage="${CANARY_PERCENTAGE:-10}"
log "INFO" "Ejecutando Canary Deployment ($canary_percentage% de tráfico)"
# Crear deployment canary
local canary_deployment="$APP_NAME-canary"
create_deployment "$environment" "$canary_deployment" "$version" 1 # 1 replica para canary
if wait_for_deployment_ready "$environment" "$canary_deployment"; then
# Configurar traffic splitting
configure_canary_traffic "$environment" "$canary_deployment" "$canary_percentage"
# Monitorear métricas durante el período de evaluación
if monitor_canary_metrics "$environment" "$canary_deployment"; then
log "INFO" "Canary exitoso, promoviendo a producción"
promote_canary_to_production "$environment" "$version"
cleanup_deployment "$environment" "$canary_deployment"
return 0
else
log "ERROR" "Canary falló las validaciones, eliminando"
cleanup_deployment "$environment" "$canary_deployment"
return 1
fi
else
log "ERROR" "Canary deployment no alcanzó estado ready"
cleanup_deployment "$environment" "$canary_deployment"
return 1
fi
}
# Crear deployment
create_deployment() {
local environment="$1"
local deployment_name="$2"
local version="$3"
local replicas="${4:-3}"
log "INFO" "Creando deployment $deployment_name con $replicas réplicas"
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: $deployment_name
namespace: $environment
labels:
app: $APP_NAME
version: $version
spec:
replicas: $replicas
selector:
matchLabels:
app: $APP_NAME
deployment: $deployment_name
template:
metadata:
labels:
app: $APP_NAME
deployment: $deployment_name
version: $version
spec:
containers:
- name: $APP_NAME
image: $REGISTRY/$APP_NAME:$version
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: ENVIRONMENT
value: "$environment"
EOF
}
# Esperar a que el deployment esté listo
wait_for_deployment_ready() {
local environment="$1"
local deployment_name="$2"
local timeout="$DEPLOY_TIMEOUT"
log "INFO" "Esperando a que $deployment_name esté listo (timeout: ${timeout}s)"
local start_time
start_time=$(date +%s)
while true; do
local current_time
current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [[ $elapsed -gt $timeout ]]; then
log "ERROR" "Timeout esperando a que $deployment_name esté listo"
return 1
fi
# Verificar estado del deployment
local ready_replicas
ready_replicas=$(kubectl get deployment "$deployment_name" -n "$environment" -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
local desired_replicas
desired_replicas=$(kubectl get deployment "$deployment_name" -n "$environment" -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0")
if [[ "$ready_replicas" == "$desired_replicas" ]] && [[ "$ready_replicas" != "0" ]]; then
log "INFO" "$deployment_name está listo ($ready_replicas/$desired_replicas réplicas)"
return 0
fi
log "DEBUG" "Esperando deployment $deployment_name: $ready_replicas/$desired_replicas réplicas listas"
sleep 10
done
}
# Verificar salud de la aplicación
verify_application_health() {
local environment="$1"
local deployment_name="$2"
local timeout="$HEALTH_CHECK_TIMEOUT"
log "INFO" "Verificando salud de $deployment_name"
# Obtener IP del servicio o usar port-forward para test
local service_ip
service_ip=$(kubectl get service "$APP_NAME" -n "$environment" -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "")
if [[ -z "$service_ip" ]]; then
log "WARN" "No se pudo obtener IP del servicio, usando port-forward para verificación"
# Crear port-forward temporal
kubectl port-forward deployment/"$deployment_name" -n "$environment" 8080:8080 &
local pf_pid=$!
sleep 5
service_ip="localhost"
fi
local start_time
start_time=$(date +%s)
local success_count=0
local required_success=5 # Requiere 5 checks exitosos consecutivos
while true; do
local current_time
current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [[ $elapsed -gt $timeout ]]; then
log "ERROR" "Timeout en verificación de salud de $deployment_name"
[[ -n "${pf_pid:-}" ]] && kill "$pf_pid" 2>/dev/null || true
return 1
fi
# Health check
if curl -s --max-time 5 "http://$service_ip:8080/health" | grep -q "ok"; then
((success_count++))
log "DEBUG" "Health check exitoso para $deployment_name ($success_count/$required_success)"
if [[ $success_count -ge $required_success ]]; then
log "INFO" "Verificación de salud completada para $deployment_name"
[[ -n "${pf_pid:-}" ]] && kill "$pf_pid" 2>/dev/null || true
return 0
fi
else
log "DEBUG" "Health check falló para $deployment_name, reiniciando contador"
success_count=0
fi
sleep 5
done
}
# Monitorear métricas de canary
monitor_canary_metrics() {
local environment="$1"
local canary_deployment="$2"
local monitoring_duration="${CANARY_MONITORING_DURATION:-300}" # 5 minutos por defecto
log "INFO" "Monitoreando métricas de canary por ${monitoring_duration} segundos"
local start_time
start_time=$(date +%s)
local error_threshold=5 # Máximo 5% de errores
local response_time_threshold=2000 # Máximo 2 segundos
while true; do
local current_time
current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [[ $elapsed -gt $monitoring_duration ]]; then
log "INFO" "Monitoreo de canary completado exitosamente"
return 0
fi
# Verificar métricas (requiere Prometheus/métricas disponibles)
local error_rate
error_rate=$(get_error_rate_for_deployment "$environment" "$canary_deployment" || echo "0")
local avg_response_time
avg_response_time=$(get_avg_response_time_for_deployment "$environment" "$canary_deployment" || echo "0")
log "DEBUG" "Canary métricas - Error rate: ${error_rate}%, Response time: ${avg_response_time}ms"
# Verificar umbrales
if (( $(echo "$error_rate > $error_threshold" | bc -l) )); then
log "ERROR" "Canary falló: error rate ${error_rate}% > ${error_threshold}%"
return 1
fi
if [[ ${avg_response_time%.*} -gt $response_time_threshold ]]; then
log "ERROR" "Canary falló: tiempo de respuesta ${avg_response_time}ms > ${response_time_threshold}ms"
return 1
fi
sleep 30
done
}
# Rollback automático
rollback_deployment() {
local environment="$1"
local previous_image="$2"
log "INFO" "Iniciando rollback automático a imagen: $previous_image"
# Rollback usando kubectl
kubectl set image deployment/"$APP_NAME" -n "$environment" "$APP_NAME"="$previous_image"
# Esperar a que el rollback complete
if kubectl rollout status deployment/"$APP_NAME" -n "$environment" --timeout="${ROLLBACK_TIMEOUT}s"; then
log "INFO" "Rollback completado exitosamente"
# Verificar que la aplicación está funcionando
if verify_application_health "$environment" "$APP_NAME"; then
log "INFO" "Aplicación funcionando correctamente post-rollback"
send_alert "INFO" "Rollback exitoso para $APP_NAME en $environment"
return 0
else
log "FATAL" "Aplicación no funciona correctamente post-rollback"
send_alert "CRITICAL" "Rollback falló para $APP_NAME en $environment - intervención manual requerida"
return 1
fi
else
log "FATAL" "Rollback no completó dentro del timeout"
send_alert "CRITICAL" "Rollback timeout para $APP_NAME en $environment - intervención manual requerida"
return 1
fi
}
# Función principal
main() {
local environment="${1:-}"
local version="${2:-}"
local strategy="${3:-blue-green}"
if [[ -z "$environment" ]] || [[ -z "$version" ]]; then
log "ERROR" "Uso: $0 <environment> <version> [strategy]"
echo "Estrategias disponibles: blue-green, rolling, canary"
exit 1
fi
# Configuración específica por entorno
case "$environment" in
dev)
DEPLOY_TIMEOUT=300
MIN_HEALTHY_INSTANCES=1
;;
staging)
DEPLOY_TIMEOUT=600
MIN_HEALTHY_INSTANCES=2
;;
prod)
DEPLOY_TIMEOUT=900
MIN_HEALTHY_INSTANCES=3
# Confirmación adicional para producción
read -p "⚠️ Confirmación requerida para deployment en PRODUCCIÓN de $APP_NAME:$version. ¿Continuar? (yes/no) " -r
if [[ ! $REPLY == "yes" ]]; then
log "INFO" "Deployment cancelado por el usuario"
exit 0
fi
;;
*)
log "FATAL" "Entorno desconocido: $environment"
;;
esac
# Ejecutar deployment
if deploy_application "$environment" "$version" "$strategy"; then
log "INFO" "✅ Deployment exitoso de $APP_NAME:$version en $environment"
send_alert "INFO" "Deployment exitoso: $APP_NAME:$version en $environment"
exit 0
else
log "ERROR" "❌ Deployment falló para $APP_NAME:$version en $environment"
send_alert "ERROR" "Deployment falló: $APP_NAME:$version en $environment"
exit 1
fi
}
# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Automatización de Infraestructura y Configuración
Script para Provisioning Completo
#!/bin/bash
#
# provision-infrastructure.sh - Provisioning completo de infraestructura
#
set -euo pipefail
# Configuración
readonly TERRAFORM_DIR="./terraform"
readonly ANSIBLE_PLAYBOOK_DIR="./ansible"
readonly INVENTORY_FILE="./inventory/hosts.yml"
readonly STATE_BUCKET="company-terraform-state"
# Función principal de provisioning
provision_infrastructure() {
local environment="$1"
local action="${2:-apply}" # plan, apply, destroy
log "INFO" "Iniciando provisioning de infraestructura para $environment"
validate_prerequisites
setup_terraform_backend "$environment"
case "$action" in
plan)
terraform_plan "$environment"
;;
apply)
terraform_plan "$environment"
confirm_terraform_apply "$environment"
terraform_apply "$environment"
configure_infrastructure "$environment"
validate_infrastructure "$environment"
;;
destroy)
confirm_terraform_destroy "$environment"
terraform_destroy "$environment"
;;
*)
log "FATAL" "Acción desconocida: $action. Usar: plan, apply, destroy"
;;
esac
}
# Validación de prerrequisitos
validate_prerequisites() {
log "INFO" "Validando prerrequisitos"
local required_tools=("terraform" "ansible" "aws" "jq" "yq")
for tool in "${required_tools[@]}"; do
if ! command -v "$tool" &>/dev/null; then
log "FATAL" "Herramienta requerida no encontrada: $tool"
fi
done
# Verificar credenciales AWS
if ! aws sts get-caller-identity &>/dev/null; then
log "FATAL" "Credenciales AWS no configuradas o inválidas"
fi
# Verificar versiones
local terraform_version
terraform_version=$(terraform version -json | jq -r '.terraform_version')
log "INFO" "Terraform versión: $terraform_version"
local ansible_version
ansible_version=$(ansible --version | head -1 | awk '{print $2}')
log "INFO" "Ansible versión: $ansible_version"
log "INFO" "Prerrequisitos validados"
}
# Configurar backend de Terraform
setup_terraform_backend() {
local environment="$1"
local backend_config="./terraform/backend-${environment}.hcl"
log "INFO" "Configurando backend de Terraform para $environment"
# Crear configuración de backend si no existe
if [[ ! -f "$backend_config" ]]; then
cat > "$backend_config" <EOF
bucket = "$STATE_BUCKET"
key = "terraform/$environment/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-state-lock"
encrypt = true
EOF
fi
# Inicializar Terraform
cd "$TERRAFORM_DIR"
terraform init -backend-config="$backend_config" -reconfigure
cd ..
log "INFO" "Backend de Terraform configurado"
}
# Plan de Terraform
terraform_plan() {
local environment="$1"
local var_file="./terraform/environments/${environment}.tfvars"
local plan_file="./terraform/plans/${environment}.tfplan"
log "INFO" "Generando plan de Terraform para $environment"
mkdir -p "./terraform/plans"
# Verificar que existe el archivo de variables
if [[ ! -f "$var_file" ]]; then
log "FATAL" "Archivo de variables no encontrado: $var_file"
fi
cd "$TERRAFORM_DIR"
# Generar plan
terraform plan \
-var-file="$var_file" \
-var="environment=$environment" \
-out="$plan_file" \
-detailed-exitcode
local plan_exit_code=$?
cd ..
case $plan_exit_code in
0)
log "INFO" "No hay cambios en la infraestructura"
;;
1)
log "FATAL" "Error al generar plan de Terraform"
;;
2)
log "INFO" "Plan generado con cambios pendientes"
;;
esac
# Mostrar resumen del plan
terraform -chdir="$TERRAFORM_DIR" show -json "$plan_file" | \
jq -r '.resource_changes[] | "\(.change.actions[0]) \(.address)"' | \
sort | uniq -c | sort -nr
return $plan_exit_code
}
# Confirmación para aplicar cambios
confirm_terraform_apply() {
local environment="$1"
# Para producción, requiere confirmación explícita
if [[ "$environment" == "prod" ]]; then
echo "⚠️ ATENCIÓN: Vas a aplicar cambios en PRODUCCIÓN"
echo "Plan de cambios:"
terraform -chdir="$TERRAFORM_DIR" show "./plans/${environment}.tfplan"
read -p "¿Confirmas que quieres aplicar estos cambios en PRODUCCIÓN? Escribe 'yes' para continuar: " -r
if [[ "$REPLY" != "yes" ]]; then
log "INFO" "Operación cancelada por el usuario"
exit 0
fi
else
echo "Aplicando cambios en entorno: $environment"
read -p "¿Continuar? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log "INFO" "Operación cancelada por el usuario"
exit 0
fi
fi
}
# Aplicar cambios de Terraform
terraform_apply() {
local environment="$1"
local plan_file="./terraform/plans/${environment}.tfplan"
log "INFO" "Aplicando cambios de Terraform para $environment"
cd "$TERRAFORM_DIR"
# Aplicar el plan pre-generado
if ! terraform apply "$plan_file"; then
log "FATAL" "Falló la aplicación de cambios de Terraform"
fi
cd ..
# Generar inventario para Ansible
generate_ansible_inventory "$environment"
log "INFO" "Cambios de Terraform aplicados exitosamente"
}
# Generar inventario de Ansible
generate_ansible_inventory() {
local environment="$1"
log "INFO" "Generando inventario de Ansible para $environment"
mkdir -p "$(dirname "$INVENTORY_FILE")"
# Obtener outputs de Terraform
cd "$TERRAFORM_DIR"
local terraform_outputs
terraform_outputs=$(terraform output -json)
cd ..
# Generar inventario YAML
cat > "$INVENTORY_FILE" <EOF
all:
vars:
environment: $environment
ansible_user: ec2-user
ansible_ssh_private_key_file: ~/.ssh/${environment}-key.pem
children:
web_servers:
hosts:
EOF
# Extraer IPs de servidores web
echo "$terraform_outputs" | jq -r '.web_server_ips.value[]?' | while read -r ip; do
echo " $ip:" >> "$INVENTORY_FILE"
done
cat >> "$INVENTORY_FILE" <EOF
app_servers:
hosts:
EOF
# Extraer IPs de servidores de aplicación
echo "$terraform_outputs" | jq -r '.app_server_ips.value[]?' | while read -r ip; do
echo " $ip:" >> "$INVENTORY_FILE"
done
cat >> "$INVENTORY_FILE" <EOF
db_servers:
hosts:
EOF
# Extraer IPs de servidores de base de datos
echo "$terraform_outputs" | jq -r '.db_server_ips.value[]?' | while read -r ip; do
echo " $ip:" >> "$INVENTORY_FILE"
done
log "INFO" "Inventario de Ansible generado: $INVENTORY_FILE"
}
# Configurar infraestructura con Ansible
configure_infrastructure() {
local environment="$1"
log "INFO" "Configurando infraestructura con Ansible para $environment"
# Esperar a que las instancias estén accesibles
wait_for_instances_ready "$environment"
# Ejecutar playbooks de Ansible
local playbooks=(
"common.yml"
"security-hardening.yml"
"web-servers.yml"
"app-servers.yml"
"db-servers.yml"
"monitoring.yml"
)
for playbook in "${playbooks[@]}"; do
local playbook_path="$ANSIBLE_PLAYBOOK_DIR/$playbook"
if [[ -f "$playbook_path" ]]; then
log "INFO" "Ejecutando playbook: $playbook"
ansible-playbook \
-i "$INVENTORY_FILE" \
-e "environment=$environment" \
"$playbook_path"
if [[ $? -ne 0 ]]; then
log "ERROR" "Falló el playbook: $playbook"
return 1
fi
else
log "WARN" "Playbook no encontrado: $playbook_path"
fi
done
log "INFO" "Configuración con Ansible completada"
}
# Esperar a que las instancias estén listas
wait_for_instances_ready() {
local environment="$1"
local timeout=300 # 5 minutos
log "INFO" "Esperando a que las instancias estén accesibles"
# Obtener IPs de todas las instancias
local all_ips
all_ips=$(yq eval '.all.children.*.hosts | keys | .[]' "$INVENTORY_FILE")
local start_time
start_time=$(date +%s)
for ip in $all_ips; do
log "INFO" "Verificando conectividad con $ip"
while true; do
local current_time
current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [[ $elapsed -gt $timeout ]]; then
log "ERROR" "Timeout esperando conectividad con $ip"
return 1
fi
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ec2-user@"$ip" "echo 'OK'" &>/dev/null; then
log "INFO" "Conectividad establecida con $ip"
break
fi
log "DEBUG" "Esperando conectividad con $ip..."
sleep 10
done
done
log "INFO" "Todas las instancias están accesibles"
}
# Validar infraestructura
validate_infrastructure() {
local environment="$1"
log "INFO" "Validando infraestructura para $environment"
# Verificar servicios críticos
local validation_playbook="$ANSIBLE_PLAYBOOK_DIR/validate-infrastructure.yml"
if [[ -f "$validation_playbook" ]]; then
log "INFO" "Ejecutando validaciones con Ansible"
ansible-playbook -i "$INVENTORY_FILE" "$validation_playbook"
fi
# Verificar conectividad de red
validate_network_connectivity "$environment"
# Verificar servicios web
validate_web_services "$environment"
log "INFO" "Validación de infraestructura completada"
}
# Validar conectividad de red
validate_network_connectivity() {
local environment="$1"
log "INFO" "Validando conectividad de red"
# Obtener Load Balancer URL de Terraform outputs
cd "$TERRAFORM_DIR"
local lb_url
lb_url=$(terraform output -raw load_balancer_url 2>/dev/null || echo "")
cd ..
if [[ -n "$lb_url" ]]; then
log "INFO" "Verificando Load Balancer: $lb_url"
if curl -s --max-time 10 "$lb_url/health" | grep -q "ok"; then
log "INFO" "Load Balancer respondiendo correctamente"
else
log "ERROR" "Load Balancer no está respondiendo correctamente"
return 1
fi
fi
}
# Validar servicios web
validate_web_services() {
local environment="$1"
log "INFO" "Validando servicios web"
# Verificar cada servidor web individualmente
local web_ips
web_ips=$(yq eval '.all.children.web_servers.hosts | keys | .[]' "$INVENTORY_FILE")
for ip in $web_ips; do
log "INFO" "Verificando servidor web: $ip"
if curl -s --max-time 10 "http://$ip/health" | grep -q "ok"; then
log "INFO" "Servidor web $ip respondiendo correctamente"
else
log "WARN" "Servidor web $ip no está respondiendo"
fi
done
}
# Destruir infraestructura
confirm_terraform_destroy() {
local environment="$1"
echo "⚠️ PELIGRO: Vas a DESTRUIR toda la infraestructura de $environment"
echo "Esta acción es IRREVERSIBLE y eliminará:"
echo "- Todas las instancias EC2"
echo "- Todos los datos en volúmenes"
echo "- Configuraciones de red"
echo "- Load Balancers y otros recursos"
read -p "¿Estás ABSOLUTAMENTE SEGURO de que quieres destruir la infraestructura de $environment? Escribe 'destroy-$environment' para confirmar: " -r
if [[ "$REPLY" != "destroy-$environment" ]]; then
log "INFO" "Operación de destrucción cancelada"
exit 0
fi
# Segunda confirmación para producción
if [[ "$environment" == "prod" ]]; then
read -p "ÚLTIMA CONFIRMACIÓN: Escribe 'DESTROY-PRODUCTION-NOW' para continuar: " -r
if [[ "$REPLY" != "DESTROY-PRODUCTION-NOW" ]]; then
log "INFO" "Operación de destrucción cancelada"
exit 0
fi
fi
}
# Destruir con Terraform
terraform_destroy() {
local environment="$1"
local var_file="./terraform/environments/${environment}.tfvars"
log "INFO" "Destruyendo infraestructura para $environment"
cd "$TERRAFORM_DIR"
terraform destroy \
-var-file="$var_file" \
-var="environment=$environment" \
-auto-approve
cd ..
# Limpiar archivos locales
rm -f "$INVENTORY_FILE"
rm -f "./terraform/plans/${environment}.tfplan"
log "INFO" "Infraestructura de $environment destruida"
send_alert "WARNING" "Infraestructura de $environment ha sido destruida"
}
# Función principal
main() {
local environment="${1:-}"
local action="${2:-apply}"
if [[ -z "$environment" ]]; then
cat <EOF
Uso: $0 <environment> [action]
Environments: dev, staging, prod
Actions: plan, apply, destroy
Ejemplos:
$0 dev plan # Generar plan para desarrollo
$0 staging apply # Aplicar cambios en staging
$0 prod destroy # Destruir infraestructura de producción
EOF
exit 1
fi
# Validar entorno
case "$environment" in
dev|staging|prod)
log "INFO" "Procesando entorno: $environment"
;;
*)
log "FATAL" "Entorno inválido: $environment. Usar: dev, staging, prod"
;;
esac
# Ejecutar provisioning
provision_infrastructure "$environment" "$action"
log "INFO" "✅ Operación completada para $environment"
}
# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Mejores Prácticas de Seguridad en Bash
Hardening y Configuración Segura
#!/bin/bash
#
# security-hardening.sh - Script para hardening de sistemas Linux
#
set -euo pipefail
# Configuración de seguridad
readonly AUDIT_LOG="/var/log/security-hardening.log"
readonly BACKUP_DIR="/root/security-backups"
readonly HARDENING_LEVEL="${HARDENING_LEVEL:-medium}" # basic, medium, strict
# Función principal de hardening
main_hardening() {
log "INFO" "Iniciando hardening de seguridad - Nivel: $HARDENING_LEVEL"
create_security_backup
system_updates
configure_ssh_hardening
configure_firewall
disable_unnecessary_services
configure_file_permissions
configure_audit_logging
configure_login_security
install_security_tools
configure_network_security
generate_security_report
log "INFO" "Hardening de seguridad completado"
}
# Crear backup de configuraciones críticas
create_security_backup() {
log "INFO" "Creando backup de configuraciones de seguridad"
mkdir -p "$BACKUP_DIR"
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
# Backup de archivos críticos
local critical_files=(
"/etc/ssh/sshd_config"
"/etc/sudoers"
"/etc/passwd"
"/etc/shadow"
"/etc/group"
"/etc/fstab"
"/etc/hosts"
"/etc/sysctl.conf"
)
for file in "${critical_files[@]}"; do
if [[ -f "$file" ]]; then
cp "$file" "$BACKUP_DIR/$(basename $file).${timestamp}"
log "DEBUG" "Backup creado: $file"
fi
done
}
# Actualización del sistema
system_updates() {
log "INFO" "Actualizando sistema y aplicando parches de seguridad"
# Detectar distribución
if command -v apt-get &>/dev/null; then
# Ubuntu/Debian
apt-get update
apt-get upgrade -y
apt-get autoremove -y
# Instalar actualizaciones de seguridad automáticas
apt-get install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
elif command -v yum &>/dev/null; then
# RHEL/CentOS
yum update -y
yum install -y yum-cron
systemctl enable yum-cron
systemctl start yum-cron
elif command -v dnf &>/dev/null; then
# Fedora
dnf update -y
dnf install -y dnf-automatic
systemctl enable dnf-automatic.timer
systemctl start dnf-automatic.timer
fi
log "INFO" "Actualizaciones del sistema completadas"
}
# Hardening de SSH
configure_ssh_hardening() {
log "INFO" "Configurando hardening de SSH"
local ssh_config="/etc/ssh/sshd_config"
# Crear configuración SSH segura
cat > "$ssh_config" <'EOF'
# SSH Hardening Configuration
Port 22
Protocol 2
# Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
# Security settings
AllowUsers deploy ubuntu ec2-user
DenyUsers root
MaxAuthTries 3
MaxStartups 10:30:60
ClientAliveInterval 600
ClientAliveCountMax 3
# Disable unused features
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitUserEnvironment no
# Logging
SyslogFacility AUTH
LogLevel INFO
# Crypto settings
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521
# Banner
Banner /etc/ssh/ssh_banner
EOF
# Crear banner de seguridad
cat > "/etc/ssh/ssh_banner" <'EOF'
***********************************************************************
* AUTHORIZED ACCESS ONLY *
* *
* This system is for authorized users only. All activity is *
**monitored and recorded. Unauthorized access is prohibited and *
**will be prosecuted to the full extent of the law. *
***********************************************************************
EOF
# Verificar configuración y reiniciar
if sshd -t; then
systemctl reload sshd
log "INFO" "SSH hardening aplicado exitosamente"
else
log "ERROR" "Error en configuración SSH, restaurando backup"
cp "$BACKUP_DIR/sshd_config."* /etc/ssh/sshd_config
systemctl reload sshd
fi
}
# Configurar firewall
configure_firewall() {
log "INFO" "Configurando firewall"
# Instalar y configurar UFW (Ubuntu) o firewalld (RHEL)
if command -v ufw &>/dev/null; then
# Ubuntu UFW
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
# Permitir SSH
ufw allow 22/tcp
# Permitir servicios web si están instalados
if systemctl is-active --quiet nginx apache2; then
ufw allow 80/tcp
ufw allow 443/tcp
fi
# Habilitar UFW
ufw --force enable
log "INFO" "UFW configurado y habilitado"
elif command -v firewall-cmd &>/dev/null; then
# RHEL firewalld
systemctl enable firewalld
systemctl start firewalld
# Configuración básica
firewall-cmd --set-default-zone=public
firewall-cmd --permanent --remove-service=dhcpv6-client
firewall-cmd --permanent --add-service=ssh
# Servicios web si están activos
if systemctl is-active --quiet nginx httpd; then
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
fi
firewall-cmd --reload
log "INFO" "firewalld configurado y habilitado"
else
# Fallback a iptables básico
log "WARN" "Configurando iptables básico"
configure_basic_iptables
fi
}
# Configuración básica de iptables
configure_basic_iptables() {
# Limpiar reglas existentes
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Políticas por defecto
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Permitir loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Permitir conexiones establecidas
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Permitir SSH
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
# Permitir HTTP/HTTPS si hay servidor web
if systemctl is-active --quiet nginx apache2 httpd; then
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
fi
# Protección contra ataques
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
# Guardar reglas
if command -v iptables-save &>/dev/null; then
iptables-save > /etc/iptables/rules.v4
fi
log "INFO" "iptables básico configurado"
}
# Deshabilitar servicios innecesarios
disable_unnecessary_services() {
log "INFO" "Deshabilitando servicios innecesarios"
local services_to_disable=(
"avahi-daemon"
"cups"
"nfs-client.target"
"rpcbind"
"telnet"
"rsh"
"rlogin"
"tftp"
"xinetd"
"ypbind"
"bluetooth"
)
for service in "${services_to_disable[@]}"; do
if systemctl is-enabled "$service" &>/dev/null; then
systemctl disable "$service"
systemctl stop "$service"
log "DEBUG" "Servicio deshabilitado: $service"
fi
done
# Remover paquetes innecesarios
if command -v apt-get &>/dev/null; then
local packages_to_remove=(
"telnet"
"rsh-client"
"rsh-redone-client"
"talk"
"ntalk"
"finger"
"xinetd"
)
for package in "${packages_to_remove[@]}"; do
if dpkg -l | grep -q "^ii.*$package"; then
apt-get remove -y "$package"
log "DEBUG" "Paquete removido: $package"
fi
done
fi
}
# Configurar permisos de archivos críticos
configure_file_permissions() {
log "INFO" "Configurando permisos de archivos críticos"
# Archivos de configuración críticos
local critical_files=(
"/etc/passwd:644"
"/etc/shadow:600"
"/etc/group:644"
"/etc/gshadow:600"
"/etc/ssh/sshd_config:600"
"/etc/sudoers:440"
"/boot/grub/grub.cfg:600"
"/etc/crontab:600"
)
for file_perm in "${critical_files[@]}"; do
local file="${file_perm%:*}"
local perm="${file_perm#*:}"
if [[ -f "$file" ]]; then
chmod "$perm" "$file"
log "DEBUG" "Permisos configurados: $file ($perm)"
fi
done
# Directorios críticos
local critical_dirs=(
"/etc/ssh:755"
"/etc/cron.d:700"
"/etc/cron.daily:700"
"/etc/cron.hourly:700"
"/etc/cron.monthly:700"
"/etc/cron.weekly:700"
"/var/log:750"
)
for dir_perm in "${critical_dirs[@]}"; do
local dir="${dir_perm%:*}"
local perm="${dir_perm#*:}"
if [[ -d "$dir" ]]; then
chmod "$perm" "$dir"
log "DEBUG" "Permisos de directorio configurados: $dir ($perm)"
fi
done
# Remover archivos world-writable peligrosos
find / -type f -perm -002 -exec ls -la {} \; 2>/dev/null | \
grep -v "/proc\|/sys\|/dev" | head -20 | \
while read -r line; do
log "WARN" "Archivo world-writable encontrado: $line"
done
}
# Configurar logging de auditoría
configure_audit_logging() {
log "INFO" "Configurando sistema de auditoría"
# Instalar auditd
if command -v apt-get &>/dev/null; then
apt-get install -y auditd audispd-plugins
elif command -v yum &>/dev/null; then
yum install -y audit
elif command -v dnf &>/dev/null; then
dnf install -y audit
fi
# Configurar reglas de auditoría
cat > "/etc/audit/rules.d/audit.rules" <'EOF'
# Audit Rules for Security Monitoring
# Delete all existing rules
-D
# Buffer size
-b 8192
# Failure mode (0=silent, 1=printk, 2=panic)
-f 1
# Monitor authentication events
-w /var/log/auth.log -p wa -k authentication
-w /var/log/secure -p wa -k authentication
# Monitor system configuration changes
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k ssh_config
# Monitor system calls
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time_change
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time_change
# Monitor file deletions
-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -k delete
# Monitor privilege escalation
-a always,exit -F arch=b64 -S setuid -S setgid -S setreuid -S setregid -k privilege_escalation
# Make configuration immutable
-e 2
EOF
# Reiniciar servicio
systemctl enable auditd
systemctl restart auditd
log "INFO" "Sistema de auditoría configurado"
}
# Configurar seguridad de login
configure_login_security() {
log "INFO" "Configurando seguridad de login"
# Configurar PAM para bloqueo de cuentas
if [[ -f "/etc/pam.d/common-auth" ]]; then
# Ubuntu/Debian
if ! grep -q "pam_tally2" /etc/pam.d/common-auth; then
sed -i '1i auth required pam_tally2.so deny=5 unlock_time=900 onerr=fail' /etc/pam.d/common-auth
fi
elif [[ -f "/etc/pam.d/system-auth" ]]; then
# RHEL/CentOS
if ! grep -q "pam_faillock" /etc/pam.d/system-auth; then
authconfig --enablefaillock --faillockargs="deny=5 unlock_time=900" --update
fi
fi
# Configurar política de contraseñas
cat > "/etc/security/pwquality.conf" <'EOF'
# Password Quality Configuration
minlen = 12
minclass = 3
maxrepeat = 2
maxclasschars = 4
difok = 8
gecoscheck = 1
dictcheck = 1
usercheck = 1
enforcing = 1
EOF
# Configurar límites de recursos
cat > "/etc/security/limits.conf" <'EOF'
# Resource Limits Configuration
# Core dumps disabled for all users
**hard core 0
# Maximum number of processes
**hard nproc 10000
**soft nproc 5000
# Maximum number of open files
**hard nofile 65536
**soft nofile 32768
# Maximum file size
**hard fsize unlimited
**soft fsize unlimited
# Maximum memory locked
**hard memlock unlimited
**soft memlock unlimited
EOF
# Configurar timeout de sesión
echo 'export TMOUT=1800' >> /etc/profile
echo 'readonly TMOUT' >> /etc/profile
log "INFO" "Seguridad de login configurada"
}
# Instalar herramientas de seguridad
install_security_tools() {
log "INFO" "Instalando herramientas de seguridad"
local security_tools=()
if command -v apt-get &>/dev/null; then
security_tools=(
"fail2ban"
"rkhunter"
"chkrootkit"
"clamav"
"aide"
"lynis"
"nmap"
"netstat-nat"
)
apt-get update
for tool in "${security_tools[@]}"; do
if ! dpkg -l | grep -q "^ii.*$tool"; then
apt-get install -y "$tool"
log "DEBUG" "Herramienta instalada: $tool"
fi
done
elif command -v yum &>/dev/null || command -v dnf &>/dev/null; then
security_tools=(
"fail2ban"
"rkhunter"
"chkrootkit"
"clamav"
"aide"
"nmap"
)
local installer="yum"
command -v dnf &>/dev/null && installer="dnf"
for tool in "${security_tools[@]}"; do
if ! rpm -qa | grep -q "$tool"; then
$installer install -y "$tool"
log "DEBUG" "Herramienta instalada: $tool"
fi
done
fi
# Configurar fail2ban
configure_fail2ban
log "INFO" "Herramientas de seguridad instaladas"
}
# Configurar fail2ban
configure_fail2ban() {
log "INFO" "Configurando fail2ban"
cat > "/etc/fail2ban/jail.local" <'EOF'
[DEFAULT]
# Ban hosts for 1 hour
bantime = 3600
# A host is banned if it has generated "maxretry" during the last "findtime"
findtime = 600
maxretry = 5
# "ignoreip" can be an IP address, a CIDR mask or a DNS host
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 6
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
[apache-auth]
enabled = true
filter = apache-auth
logpath = /var/log/apache*/*error.log
maxretry = 6
[apache-badbots]
enabled = true
filter = apache-badbots
logpath = /var/log/apache*/*access.log
maxretry = 2
EOF
# Reiniciar fail2ban
systemctl enable fail2ban
systemctl restart fail2ban
log "INFO" "fail2ban configurado y habilitado"
}
# Configurar seguridad de red
configure_network_security() {
log "INFO" "Configurando seguridad de red"
# Configurar parámetros del kernel para seguridad
cat > "/etc/sysctl.d/99-security.conf" <'EOF'
# Network Security Configuration
# IP Spoofing protection
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# Log Martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1
# Ignore Directed pings
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
# TCP SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
# Control memory allocation
vm.mmap_min_addr = 65536
# Hide kernel pointers
kernel.kptr_restrict = 1
# Disable core dumps
fs.suid_dumpable = 0
# Control dmesg access
kernel.dmesg_restrict = 1
EOF
# Aplicar configuración
sysctl --system
log "INFO" "Seguridad de red configurada"
}
# Generar reporte de seguridad
generate_security_report() {
local report_file="/root/security-report-$(date +%Y%m%d_%H%M%S).txt"
log "INFO" "Generando reporte de seguridad: $report_file"
cat > "$report_file" <EOF
SECURITY HARDENING REPORT
Generated: $(date)
Hostname: $(hostname)
Hardening Level: $HARDENING_LEVEL
=== SYSTEM INFORMATION ===
OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)
Kernel: $(uname -r)
Uptime: $(uptime -p)
=== SERVICES STATUS ===
SSH Status: $(systemctl is-active sshd || echo "inactive")
Firewall Status: $(systemctl is-active ufw || systemctl is-active firewalld || echo "inactive")
Fail2ban Status: $(systemctl is-active fail2ban || echo "inactive")
Audit Status: $(systemctl is-active auditd || echo "inactive")
=== NETWORK CONFIGURATION ===
Open Ports:
$(ss -tuln)
=== USER ACCOUNTS ===
Regular Users:
$(awk -F: '$3 >= 1000 && $1 != "nobody" {print $1}' /etc/passwd)
Users with sudo privileges:
$(grep -Po '^sudo.+:\K.*$' /etc/group 2>/dev/null || echo "N/A")
=== SECURITY TOOLS ===
EOF
# Verificar herramientas instaladas
local security_tools=("fail2ban" "rkhunter" "chkrootkit" "clamav" "aide" "lynis")
for tool in "${security_tools[@]}"; do
if command -v "$tool" &>/dev/null; then
echo "$tool: INSTALLED" >> "$report_file"
else
echo "$tool: NOT INSTALLED" >> "$report_file"
fi
done
cat >> "$report_file" <EOF
=== RECENT SECURITY EVENTS ===
SSH Login Attempts (last 24h):
$(grep "$(date --date='1 day ago' '+%b %d')" /var/log/auth.log 2>/dev/null | grep "sshd" | wc -l || echo "N/A")
Failed Login Attempts:
$(grep "Failed password" /var/log/auth.log 2>/dev/null | tail -5 || echo "N/A")
=== RECOMMENDATIONS ===
1. Regularly update system packages
2. Review user accounts and remove unused ones
3. Monitor logs for suspicious activity
4. Run security scans with installed tools
5. Backup critical configuration files
EOF
log "INFO" "Reporte de seguridad generado: $report_file"
# Enviar reporte por email si está configurado
if [[ -n "${SECURITY_EMAIL:-}" ]] && command -v mail &>/dev/null; then
mail -s "Security Hardening Report - $(hostname)" "$SECURITY_EMAIL" "$report_file"
log "INFO" "Reporte enviado por email a $SECURITY_EMAIL"
fi
}
# Función principal
main() {
local action="${1:-hardening}"
case "$action" in
hardening)
main_hardening
;;
report)
generate_security_report
;;
*)
cat <EOF
Uso: $0 [action]
Actions:
hardening - Ejecutar hardening completo del sistema (por defecto)
report - Generar solo reporte de seguridad
Variables de entorno:
HARDENING_LEVEL - Nivel de hardening (basic|medium|strict) [default: medium]
SECURITY_EMAIL - Email para enviar reportes de seguridad
Ejemplo:
HARDENING_LEVEL=strict $0 hardening
$0 report
EOF
exit 1
;;
esac
}
# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Debugging y Optimización de Scripts
Herramientas y Técnicas de Debugging
#!/bin/bash
#
# debug-helper.sh - Herramientas y técnicas de debugging para Bash
#
# Habilitar debugging estricto
set -euo pipefail
# Variables de debugging
DEBUG_LEVEL="${DEBUG_LEVEL:-1}" # 0=off, 1=basic, 2=verbose, 3=trace
DEBUG_LOG="${DEBUG_LOG:-/tmp/debug.log}"
# Función de debugging mejorada
debug() {
local level="$1"
local message="$2"
local caller="${BASH_SOURCE[2]##*/}:${BASH_LINENO[1]}"
if [[ $level -le $DEBUG_LEVEL ]]; then
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S.%3N')
echo "[$timestamp] DEBUG[$level] $caller: $message" | tee -a "$DEBUG_LOG"
fi
}
# Profiling de funciones
profile_function() {
local func_name="$1"
shift
local start_time
start_time=$(date +%s.%N)
# Ejecutar función
"$func_name" "$@"
local result=$?
local end_time
end_time=$(date +%s.%N)
local duration
duration=$(echo "$end_time - $start_time" | bc -l)
debug 1 "Función $func_name ejecutada en ${duration}s"
return $result
}
# Validación de entrada con debugging
validate_input() {
local input="$1"
local pattern="$2"
local description="${3:-input}"
debug 2 "Validando $description: '$input' contra patrón '$pattern'"
if [[ ! $input =~ $pattern ]]; then
debug 1 "Validación falló para $description"
echo "Error: $description no válido: '$input'" >&2
echo "Patrón esperado: $pattern" >&2
return 1
fi
debug 2 "Validación exitosa para $description"
return 0
}
# Ejemplo de uso de debugging
example_function() {
debug 3 "Iniciando example_function con parámetros: $*"
local param1="${1:-default}"
local param2="${2:-}"
debug 2 "param1='$param1', param2='$param2'"
# Validar parámetros
if ! validate_input "$param1" '^[a-zA-Z0-9_-]+$' "param1"; then
return 1
fi
if [[ -n "$param2" ]] && ! validate_input "$param2" '^[0-9]+$' "param2"; then
return 1
fi
# Simular procesamiento
debug 2 "Procesando datos..."
sleep 1
debug 1 "Procesamiento completado exitosamente"
echo "Resultado: $param1 procesado"
}
# Mostrar ejemplos de debugging
show_debug_examples() {
echo "=== Ejemplos de Debugging ==="
echo
echo "1. Debugging básico (DEBUG_LEVEL=1):"
DEBUG_LEVEL=1 example_function "test123"
echo
echo "2. Debugging verbose (DEBUG_LEVEL=2):"
DEBUG_LEVEL=2 example_function "test456" "789"
echo
echo "3. Debugging con trace (DEBUG_LEVEL=3):"
DEBUG_LEVEL=3 example_function "test789"
echo
echo "4. Profiling de función:"
profile_function example_function "profile_test"
echo
echo "5. Validación con error:"
example_function "invalid@param" "not_a_number" || true
echo
}
# Ejecutar ejemplos si se llama directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
show_debug_examples
fi
Conclusión
El scripting en Bash sigue siendo una competencia fundamental en el arsenal de cualquier profesional DevOps moderno. A través de esta guía completa, hemos explorado desde los fundamentos hasta técnicas avanzadas que te permitirán automatizar eficientemente tus flujos de trabajo.
Puntos Clave para el Éxito
1. Fundamentos Sólidos
- Estructura de scripts profesional con validación de errores
- Manejo robusto de variables y parámetros
- Sistema de logging comprehensivo con diferentes niveles
2. Automatización Integral
- Pipelines CI/CD completos con validaciones de seguridad
- Deployments inteligentes con rollback automático
- Monitoreo proactivo con alertas multi-canal
3. Seguridad por Diseño
- Hardening sistemático de infraestructura
- Manejo seguro de secretos y credenciales
- Auditoría y compliance automatizados
4. Operaciones a Escala
- Provisioning de infraestructura con Terraform
- Configuración masiva con Ansible
- Monitoreo y observabilidad comprehensivos
Mejores Prácticas para Implementar
- Comienza Simple: Inicia con scripts básicos y evoluciona gradualmente hacia soluciones más complejas
- Testing Continuo: Implementa validaciones automáticas en cada etapa
- Documentación Viva: Mantén documentación actualizada y ejemplos prácticos
- Seguridad Primero: Nunca compromas la seguridad por conveniencia
- Monitoreo Constante: Implementa observabilidad desde el primer día
El Futuro de Bash en DevOps
Aunque emergen nuevas tecnologías como contenedores, Kubernetes y herramientas cloud-native, Bash mantiene su relevancia por:
- Compatibilidad Universal: Funciona en cualquier sistema Unix/Linux
- Integración Natural: Se conecta seamlessly con todas las herramientas DevOps
- Performance: Overhead mínimo para operaciones del sistema
- Simplicidad: Curva de aprendizaje accesible para cualquier nivel
La combinación de Bash con herramientas modernas como Docker, Kubernetes, y plataformas cloud crea un ecosistema poderoso que puede manejar desde tareas simples hasta orquestación compleja de infraestructura empresarial.
Próximos Pasos
- Practica Regularmente: Implementa los ejemplos en tu entorno
- Contribuye a la Comunidad: Comparte tus scripts y aprende de otros
- Mantente Actualizado: Sigue las mejores prácticas de la industria
- Integra con Moderno: Combina Bash con herramientas cloud-native
El dominio del scripting en Bash te convertirá en un profesional DevOps más eficiente, capaz de automatizar cualquier tarea y resolver problemas complejos con soluciones elegantes y mantenibles.
Recursos Adicionales
Documentación y Referencias
- GNU Bash Manual - Documentación oficial completa
- ShellCheck - Analizador estático para scripts shell
- explainshell.com - Explicaciones detalladas de comandos
- Advanced Bash-Scripting Guide - Guía avanzada comprehensiva
Herramientas de Desarrollo
- Bats - Framework de testing para Bash
- bashdb - Debugger interactivo para Bash
- shellspec - Testing framework con sintaxis BDD
- shfmt - Formateador de código shell
Colecciones de Scripts
- Awesome Bash - Recursos curados de Bash
- DevOps Bash Tools - Colección de herramientas DevOps
- bash-it - Framework de productividad para Bash
Integración con DevOps
- GitHub Actions - Automatización con scripts shell
- GitLab CI - Pipelines con Bash scripting
- Jenkins - Pipeline as Code con Bash
- Ansible - Módulo shell para automatización