From d6fc501aacfc5ca1aa21bc9e60e9700a1c1d69b6 Mon Sep 17 00:00:00 2001 From: Johnny Date: Sun, 4 Jan 2026 17:52:54 +0000 Subject: [PATCH] Actualiser system_hardening_optimized.sh --- system_hardening_optimized.sh | 2821 +++++++++++++++++---------------- 1 file changed, 1447 insertions(+), 1374 deletions(-) diff --git a/system_hardening_optimized.sh b/system_hardening_optimized.sh index 1832175..a9ed8b0 100644 --- a/system_hardening_optimized.sh +++ b/system_hardening_optimized.sh @@ -2,30 +2,61 @@ ################################################################################ # Script: system_hardening_optimized.sh -# Version: 7.0 +# Version: 8.1 # Date: $(date +%Y-%m-%d) # Author: Security Team # Description: Système de durcissement sécurité pour Debian/Ubuntu LTS -# avec détection automatique des ports ouverts +# Mode autonome avec valeurs par défaut sécurisées # License: GPLv3 ################################################################################ set -euo pipefail # ============================================================================== -# CONFIGURATION +# CONFIGURATION AUTONOME # ============================================================================== readonly LOG_FILE="/var/log/system_hardening.log" readonly STATUS_FILE="/var/log/hardening_status.log" readonly BACKUP_DIR="/root/backup_hardening_$(date +%Y%m%d_%H%M%S)" readonly SECURITY_REPORT="/var/log/security_report_$(date +%Y%m%d).log" -readonly NEW_SSH_PORT=22022 readonly OPEN_PORTS_FILE="/tmp/open_ports_detected.txt" +# Valeurs par défaut pour mode autonome +readonly DEFAULT_SSH_PORT=22022 +readonly DEFAULT_TIMEZONE="Europe/Paris" +readonly DEFAULT_PASS_MAX_DAYS=90 +readonly DEFAULT_PASS_MIN_DAYS=7 +readonly DEFAULT_UMASK="027" + +# Configuration autonome (modifiable via variables d'environnement) +: "${AUTO_SSH_PORT:=$DEFAULT_SSH_PORT}" +: "${AUTO_TIMEZONE:=$DEFAULT_TIMEZONE}" +: "${AUTO_PASS_MAX_DAYS:=$DEFAULT_PASS_MAX_DAYS}" +: "${AUTO_PASS_MIN_DAYS:=$DEFAULT_PASS_MIN_DAYS}" +: "${AUTO_UMASK:=$DEFAULT_UMASK}" +: "${AUTO_DISABLE_ROOT_LOGIN:=no}" +: "${AUTO_ENABLE_FAIL2BAN:=yes}" +: "${AUTO_ENABLE_UFW:=yes}" +: "${AUTO_ENABLE_AIDE:=yes}" +: "${AUTO_ENABLE_CLAMAV:=yes}" +: "${AUTO_SKIP_PORTS_DETECTION:=no}" +: "${AUTO_SKIP_LYNIS:=no}" +: "${AUTO_YES:=no}" +: "${AUTO_CLEANUP_SSH:=no}" + TOTAL_STEPS=31 CURRENT_STEP=1 +# Variables de contrôle +FORCE_ALL=false +FORCE_STEPS=() +SKIP_STEPS=() +RESET_ALL=false +LIST_STEPS=false +SHOW_STATUS=false +UNATTENDED=false + # ============================================================================== # COULEURS # ============================================================================== @@ -38,6 +69,44 @@ readonly BLUE='\033[0;34m' readonly MAGENTA='\033[0;35m' readonly NC='\033[0m' +# ============================================================================== +# DÉFINITION DES ÉTAPES +# ============================================================================== + +declare -A STEP_DESCRIPTIONS=( + ["install_security_tools"]="Installation des outils de sécurité" + ["detect_open_ports"]="Détection des ports ouverts" + ["configure_process_accounting"]="Configuration Process Accounting" + ["configure_sysctl_security"]="Durcissement sysctl" + ["configure_log_permissions"]="Permissions des logs" + ["configure_pam_password_policy"]="Politique mots de passe PAM" + ["configure_login_defs"]="Configuration login.defs" + ["configure_umask"]="Configuration umask" + ["configure_aide_sha512"]="Configuration AIDE SHA512" + ["initialize_aide_db"]="Initialisation base AIDE" + ["configure_clamav"]="Configuration ClamAV" + ["configure_chrony"]="Configuration Chrony" + ["harden_ssh"]="Durcissement SSH" + ["configure_banners"]="Configuration bannières" + ["configure_firewall_ports"]="Configuration pare-feu UFW" + ["configure_fail2ban"]="Configuration Fail2ban" + ["remove_unneeded_packages"]="Suppression paquets inutiles" + ["restrict_file_permissions"]="Restriction permissions fichiers" + ["disable_risky_kernel_modules"]="Désactivation modules noyau" + ["configure_security_limits"]="Configuration limites sécurité" + ["verify_packages_integrity"]="Vérification intégrité paquets" + ["configure_automatic_updates"]="Configuration mises à jour auto" + ["configure_aide_cron"]="Configuration tâche AIDE cron" + ["harden_smtp_banner"]="Durcissement bannière SMTP" + ["harden_systemd_services"]="Durcissement services systemd" + ["configure_advanced_pam"]="Configuration PAM avancée" + ["check_partition_layout"]="Vérification partitions" + ["check_vmlinuz"]="Vérification fichiers noyau" + ["run_chkrootkit"]="Exécution chkrootkit" + ["prepare_ssh_cleanup"]="Préparation nettoyage SSH" + ["run_lynis_audit"]="Audit Lynis final" +) + # ============================================================================== # FONCTIONS UTILITAIRES # ============================================================================== @@ -77,17 +146,52 @@ print_info() { log_message "$1" "INFO" } +auto_confirm() { + [[ "$AUTO_YES" == "yes" ]] && return 0 + [[ "$UNATTENDED" == true ]] && return 0 + return 1 +} + +# ============================================================================== +# GESTION DES ÉTAPES +# ============================================================================== + check_step_done() { - grep -q "^${1}$" "$STATUS_FILE" 2>/dev/null + local step_name="$1" + + if $FORCE_ALL; then + return 1 + fi + + if [[ " ${FORCE_STEPS[@]} " =~ " ${step_name} " ]]; then + print_info "Forçage de l'étape: $step_name" + return 1 + fi + + if [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then + print_info "Saut de l'étape: $step_name" + return 0 + fi + + grep -q "^${step_name}$" "$STATUS_FILE" 2>/dev/null } mark_step_done() { - echo "$1" >> "$STATUS_FILE" - log_message "Étape '$1' marquée comme terminée" "STATUS" + local step_name="$1" + + if [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then + print_info "Étape $step_name ignorée - non marquée comme terminée" + return 0 + fi + + sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true + echo "$step_name" >> "$STATUS_FILE" + log_message "Étape '$step_name' marquée comme terminée" "STATUS" } skip_step() { - print_info "Étape déjà effectuée : $1" + local step_name="$1" + print_info "Étape déjà effectuée : $step_name" CURRENT_STEP=$((CURRENT_STEP + 1)) } @@ -132,25 +236,409 @@ update_config_value() { } # ============================================================================== -# FONCTIONS DE DÉTECTION DES PORTS +# FONCTIONS DE CONTRÔLE +# ============================================================================== + +reset_step() { + local step_name="$1" + + if [[ -f "$STATUS_FILE" ]]; then + sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true + print_success "Étape '$step_name' réinitialisée" + + case $step_name in + "harden_ssh") + if [[ -f "${BACKUP_DIR}/sshd_config" ]]; then + if auto_confirm; then + cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config + systemctl restart sshd 2>/dev/null || true + print_success "Configuration SSH restaurée" + else + read -p "Restaurer la configuration SSH originale ? (o/N): " -r restore_ssh + if [[ "$restore_ssh" =~ ^[Oo]$ ]]; then + cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config + systemctl restart sshd 2>/dev/null || true + print_success "Configuration SSH restaurée" + fi + fi + fi + ;; + "configure_firewall_ports") + if command -v ufw > /dev/null 2>&1; then + if auto_confirm; then + ufw --force reset > /dev/null 2>&1 || true + print_success "Règles UFW réinitialisées" + else + read -p "Réinitialiser les règles UFW ? (o/N): " -r reset_ufw + if [[ "$reset_ufw" =~ ^[Oo]$ ]]; then + ufw --force reset > /dev/null 2>&1 || true + print_success "Règles UFW réinitialisées" + fi + fi + fi + ;; + "configure_fail2ban") + if [[ -f "${BACKUP_DIR}/jail.local" ]]; then + if auto_confirm; then + cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true + systemctl restart fail2ban 2>/dev/null || true + print_success "Configuration Fail2ban restaurée" + else + read -p "Restaurer la configuration Fail2ban originale ? (o/N): " -r restore_fail2ban + if [[ "$restore_fail2ban" =~ ^[Oo]$ ]]; then + cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true + systemctl restart fail2ban 2>/dev/null || true + print_success "Configuration Fail2ban restaurée" + fi + fi + fi + ;; + esac + else + print_warning "Fichier de statut non trouvé" + fi +} + +reset_all_steps() { + print_step "Réinitialisation de toutes les étapes" + + if ! auto_confirm; then + echo -e "${RED}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}║ ⚠ ATTENTION CRITIQUE ⚠ ║${NC}" + echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo "Cette action va:" + echo "1. Supprimer l'historique d'exécution de toutes les étapes" + echo "2. Permettre une ré-exécution complète du script" + echo "3. NE supprime PAS les configurations appliquées" + echo "" + echo -n "Confirmez-vous la réinitialisation complète ? (oui/NON): " + + read -r confirmation + + if [[ "$confirmation" != "oui" ]]; then + print_error "Réinitialisation annulée" + exit 1 + fi + else + print_info "Mode auto: réinitialisation confirmée automatiquement" + fi + + if [[ -f "$STATUS_FILE" ]]; then + cp "$STATUS_FILE" "${STATUS_FILE}.backup.$(date +%Y%m%d_%H%M%S)" + print_info "Ancien statut sauvegardé" + fi + + > "$STATUS_FILE" + print_success "Toutes les étapes ont été réinitialisées" + + if auto_confirm; then + print_info "Mode auto: aucune restauration de configuration" + return 0 + fi + + read -p "Voulez-vous restaurer les configurations originales ? (o/N): " -r restore_all + + if [[ "$restore_all" =~ ^[Oo]$ ]]; then + echo "" + echo "Sélectionnez les configurations à restaurer:" + echo "1) SSH" + echo "2) UFW (pare-feu)" + echo "3) Fail2ban" + echo "4) Toutes les configurations" + echo "5) Aucune" + echo "" + read -p "Choix (1-5): " -r restore_choice + + case $restore_choice in + 1) restore_ssh_config ;; + 2) restore_ufw_config ;; + 3) restore_fail2ban_config ;; + 4) restore_all_configs ;; + *) print_info "Aucune restauration effectuée" ;; + esac + fi +} + +configure_log_permissions() { + local step_name="configure_log_permissions" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Configuration des permissions des fichiers de log" + + # Définir les permissions correctes pour les répertoires et fichiers de logs + print_info "Configuration des permissions des logs..." + + # Répertoire /var/log + chmod 750 /var/log 2>/dev/null || true + chown root:adm /var/log 2>/dev/null || true + + # Fichiers de logs système + find /var/log -type f -exec chmod 640 {} \; 2>/dev/null || true + + # Logs sensibles avec permissions restreintes + for log_file in /var/log/auth.log /var/log/syslog /var/log/secure /var/log/messages; do + [[ -f "$log_file" ]] && { + chmod 600 "$log_file" 2>/dev/null || true + print_info " → $log_file : permissions 600" + } + done + + # Logs d'audit + [[ -d /var/log/audit ]] && { + chmod 700 /var/log/audit 2>/dev/null || true + chown root:root /var/log/audit 2>/dev/null || true + find /var/log/audit -type f -exec chmod 600 {} \; 2>/dev/null || true + print_info " → /var/log/audit : permissions 700" + } + + # Logs Apache/Nginx + for log_dir in /var/log/apache2 /var/log/nginx /var/log/httpd; do + [[ -d "$log_dir" ]] && { + chmod 750 "$log_dir" 2>/dev/null || true + find "$log_dir" -type f -name "*.log" -exec chmod 640 {} \; 2>/dev/null || true + print_info " → $log_dir : permissions 750" + } + done + + # Journal systemd + if command -v journalctl > /dev/null 2>&1; then + chmod 2750 /var/log/journal 2>/dev/null || true + chown root:systemd-journal /var/log/journal 2>/dev/null || true + print_info " → /var/log/journal : permissions 2750" + fi + + # Création des fichiers de log manquants avec permissions sécurisées + for log_file in /var/log/btmp /var/log/wtmp; do + [[ ! -f "$log_file" ]] && { + touch "$log_file" + chmod 660 "$log_file" 2>/dev/null || true + chown root:utmp "$log_file" 2>/dev/null || true + print_info " → Création $log_file : permissions 660" + } + done + + # Application du sticky bit sur /var/log + chmod +t /var/log 2>/dev/null || true + + print_success "Permissions des logs configurées" + mark_step_done "$step_name" +} + +list_all_steps() { + echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ LISTE DES ÉTAPES DISPONIBLES ║${NC}" + echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + + local i=1 + for step_name in "${!STEP_DESCRIPTIONS[@]}"; do + local status="❌ Non exécutée" + if check_step_done "$step_name" 2>/dev/null; then + status="✅ Terminée" + fi + + printf "${BLUE}%2d.${NC} %-35s ${GREEN}%-12s${NC}\n" \ + "$i" "${STEP_DESCRIPTIONS[$step_name]}" "$status" + i=$((i + 1)) + done +} + +show_step_status() { + echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ STATUT DES ÉTAPES ║${NC}" + echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + + local completed=0 + local total=${#STEP_DESCRIPTIONS[@]} + + for step_name in "${!STEP_DESCRIPTIONS[@]}"; do + local description="${STEP_DESCRIPTIONS[$step_name]}" + + if check_step_done "$step_name" 2>/dev/null; then + echo -e "✅ ${GREEN}${description}${NC}" + completed=$((completed + 1)) + else + echo -e "❌ ${RED}${description}${NC}" + fi + done + + echo "" + echo -e "${YELLOW}RÉSUMÉ:${NC}" + echo -e " Progression: ${completed}/${total} étapes" +} + +# ============================================================================== +# PARSING DES ARGUMENTS AMÉLIORÉ +# ============================================================================== + +parse_arguments() { + while [[ $# -gt 0 ]]; do + case $1 in + --force-all) + FORCE_ALL=true + print_warning "Mode FORCE ALL activé" + shift + ;; + --force-step=*) + local step="${1#*=}" + FORCE_STEPS+=("$step") + print_info "Étape forcée: $step" + shift + ;; + --skip-step=*) + local step="${1#*=}" + SKIP_STEPS+=("$step") + print_info "Étape ignorée: $step" + shift + ;; + --reset-step=*) + local step="${1#*=}" + reset_step "$step" + exit 0 + ;; + --reset-all) + reset_all_steps + exit 0 + ;; + --list-steps) + LIST_STEPS=true + shift + ;; + --show-status) + SHOW_STATUS=true + shift + ;; + --cleanup-ssh) + AUTO_CLEANUP_SSH="yes" + shift + ;; + --unattended) + UNATTENDED=true + AUTO_YES="yes" + print_info "Mode autonome activé" + shift + ;; + --ssh-port=*) + AUTO_SSH_PORT="${1#*=}" + print_info "Port SSH configuré: $AUTO_SSH_PORT" + shift + ;; + --timezone=*) + AUTO_TIMEZONE="${1#*=}" + print_info "Fuseau horaire: $AUTO_TIMEZONE" + shift + ;; + --yes|-y) + AUTO_YES="yes" + print_info "Mode auto-confirmation activé" + shift + ;; + --help|-h) + show_help + exit 0 + ;; + *) + print_error "Option inconnue: $1" + show_help + exit 1 + ;; + esac + done +} + +show_help() { + echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ AIDE - SYSTEM HARDENING SCRIPT v8.1 ║${NC}" + echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "${GREEN}USAGE:${NC}" + echo " $0 [OPTIONS]" + echo "" + echo -e "${GREEN}OPTIONS:${NC}" + echo " --force-all Forcer l'exécution de toutes les étapes" + echo " --force-step=NOM Forcer une étape spécifique" + echo " --skip-step=NOM Sauter une étape spécifique" + echo " --reset-step=NOM Réinitialiser une étape" + echo " --reset-all Réinitialiser toutes les étapes" + echo " --list-steps Lister toutes les étapes disponibles" + echo " --show-status Afficher le statut des étapes" + echo " --cleanup-ssh Nettoyer le port SSH 22 (après test)" + echo " --unattended Mode autonome (pas d'interaction)" + echo " --ssh-port=PORT Définir le port SSH personnalisé" + echo " --timezone=TZ Définir le fuseau horaire" + echo " --yes, -y Répondre 'oui' à toutes les questions" + echo " --help, -h Afficher cette aide" + echo "" + echo -e "${GREEN}VARIABLES D'ENVIRONNEMENT:${NC}" + echo " AUTO_SSH_PORT Port SSH (défaut: 22022)" + echo " AUTO_TIMEZONE Fuseau horaire (défaut: Europe/Paris)" + echo " AUTO_PASS_MAX_DAYS Max jours pour mot de passe (défaut: 90)" + echo " AUTO_PASS_MIN_DAYS Min jours pour mot de passe (défaut: 7)" + echo " AUTO_UMASK Umask par défaut (défaut: 027)" + echo " AUTO_DISABLE_ROOT_LOGIN Désactiver login root (défaut: no)" + echo " AUTO_ENABLE_FAIL2BAN Activer Fail2ban (défaut: yes)" + echo " AUTO_ENABLE_UFW Activer UFW (défaut: yes)" + echo " AUTO_ENABLE_AIDE Activer AIDE (défaut: yes)" + echo " AUTO_ENABLE_CLAMAV Activer ClamAV (défaut: yes)" + echo " AUTO_SKIP_PORTS_DETECTION Sauter détection ports (défaut: no)" + echo " AUTO_SKIP_LYNIS Sauter audit Lynis (défaut: no)" + echo " AUTO_YES Répondre oui automatiquement (défaut: no)" + echo " AUTO_CLEANUP_SSH Nettoyer SSH automatiquement (défaut: no)" + echo "" + echo -e "${GREEN}EXEMPLES:${NC}" + echo " # Mode autonome complet" + echo " $0 --unattended" + echo "" + echo " # Mode autonome avec port SSH personnalisé" + echo " AUTO_SSH_PORT=2222 $0 --unattended" + echo "" + echo " # Mode avec auto-confirmation" + echo " $0 --yes" + echo "" + echo " # Mode autonome sans certains composants" + echo " AUTO_ENABLE_CLAMAV=no AUTO_ENABLE_AIDE=no $0 --unattended" + echo "" + echo -e "${RED}⚠ AVERTISSEMENT:${NC}" + echo " En mode autonome, le script prend des décisions automatiques." + echo " Assurez-vous d'avoir testé les configurations avant en production." +} + +# ============================================================================== +# FONCTIONS DE DÉTECTION DES PORTS (AMÉLIORÉES) # ============================================================================== detect_open_ports() { local step_name="detect_open_ports" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + + if [[ "$AUTO_SKIP_PORTS_DETECTION" == "yes" ]]; then + print_info "Détection des ports ignorée (AUTO_SKIP_PORTS_DETECTION=yes)" + mark_step_done "$step_name" + return 0 + fi + + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Détection des ports ouverts" print_info "Scan des ports en écoute localement..." - # Utiliser ss pour détecter les ports en écoute - ss -tlnp | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" 2>/dev/null || { - # Fallback sur netstat si ss n'est pas disponible - netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" || { - print_warning "Impossible de détecter les ports ouverts" - touch "$OPEN_PORTS_FILE" - } - } + # Utilisation de ss (plus moderne) ou netstat + if command -v ss > /dev/null 2>&1; then + ss -tlnp | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" 2>/dev/null + elif command -v netstat > /dev/null 2>&1; then + netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" + else + print_warning "Impossible de détecter les ports ouverts" + echo "22" > "$OPEN_PORTS_FILE" # Port SSH par défaut + fi local port_count=$(wc -l < "$OPEN_PORTS_FILE" 2>/dev/null || echo 0) @@ -159,6 +647,7 @@ detect_open_ports() { print_success "$port_count port(s) ouvert(s) détecté(s)" else print_warning "Aucun port ouvert détecté" + echo "22" > "$OPEN_PORTS_FILE" # Au minimum le port SSH fi mark_step_done "$step_name" @@ -171,28 +660,29 @@ is_port_open() { get_ssh_port_to_use() { if detect_lxc; then - print_info "Conteneur LXC détecté - vérification du port 22022..." - if is_port_open "22022"; then - print_info "Port 22022 déjà ouvert dans LXC" - echo "22022" + print_info "Conteneur LXC détecté - vérification du port $AUTO_SSH_PORT..." + if is_port_open "$AUTO_SSH_PORT"; then + print_info "Port $AUTO_SSH_PORT déjà ouvert dans LXC" + echo "$AUTO_SSH_PORT" else - print_warning "Port 22022 non ouvert dans LXC - utilisation du port 22" + print_warning "Port $AUTO_SSH_PORT non ouvert dans LXC - utilisation du port 22" echo "22" fi else - # Pour les systèmes physiques/VMs, utiliser le nouveau port - echo "22022" + echo "$AUTO_SSH_PORT" fi } # ============================================================================== -# FONCTIONS DE DURCISSEMENT (adaptées) +# FONCTIONS DE DURCISSEMENT (AMÉLIORÉES) # ============================================================================== -# ÉTAPE 1: Mise à jour système et installation outils install_security_tools() { local step_name="install_security_tools" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Mise à jour système et installation outils de sécurité" @@ -202,20 +692,290 @@ install_security_tools() { print_info "Mise à jour du système..." DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq - local packages="lynis aide aide-common fail2ban ufw libpam-pwquality apt-listchanges \ - apt-listbugs needrestart clamav clamav-daemon chrony chkrootkit \ + # Liste de base des paquets + local base_packages="lynis aide aide-common ufw libpam-pwquality apt-listchanges \ + apt-listbugs needrestart chrony chkrootkit \ libpam-tmpdir debsums unattended-upgrades" - detect_container || packages+=" acct" + # Ajout conditionnel des paquets + [[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && base_packages+=" fail2ban" + [[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && base_packages+=" clamav clamav-daemon" + + # Process accounting uniquement si pas conteneur + detect_container || base_packages+=" acct" print_info "Installation des paquets de sécurité..." - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $packages + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $base_packages print_success "Système mis à jour et outils installés" mark_step_done "$step_name" } -# ÉTAPE 2: Détection des ports ouverts (déjà définie plus haut) +configure_sysctl_security() { + local step_name="configure_sysctl_security" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Durcissement des paramètres noyau (sysctl)" + + cat > /etc/sysctl.d/99-security-hardening.conf << EOF +# Protection réseau IPv4 +net.ipv4.conf.all.accept_source_route = 0 +net.ipv4.conf.all.accept_redirects = 0 +net.ipv4.conf.all.send_redirects = 0 +net.ipv4.conf.all.log_martians = 1 +net.ipv4.conf.all.rp_filter = 1 +net.ipv4.conf.default.accept_source_route = 0 +net.ipv4.conf.default.accept_redirects = 0 +net.ipv4.conf.default.log_martians = 1 +net.ipv4.icmp_echo_ignore_broadcasts = 1 +net.ipv4.icmp_ignore_bogus_error_responses = 1 +net.ipv4.tcp_syncookies = 1 +net.ipv4.tcp_max_syn_backlog = 2048 +net.ipv4.tcp_synack_retries = 2 +net.ipv4.tcp_syn_retries = 5 + +# Protection réseau IPv6 +net.ipv6.conf.all.accept_redirects = 0 +net.ipv6.conf.all.accept_source_route = 0 +net.ipv6.conf.default.accept_redirects = 0 +net.ipv6.conf.default.accept_source_route = 0 + +# Protection mémoire et exécution +kernel.randomize_va_space = 2 +kernel.kptr_restrict = 2 +kernel.dmesg_restrict = 1 +kernel.yama.ptrace_scope = 1 +kernel.unprivileged_bpf_disabled = 1 + +# Protection fichiers +fs.suid_dumpable = 0 +fs.protected_fifos = 2 +fs.protected_regular = 2 +fs.protected_symlinks = 1 +fs.protected_hardlinks = 1 + +# Limites réseau +net.core.rmem_max = 134217728 +net.core.wmem_max = 134217728 +net.ipv4.tcp_rmem = 4096 87380 134217728 +net.ipv4.tcp_wmem = 4096 65536 134217728 +net.core.netdev_max_backlog = 2500 +EOF + + sysctl -p /etc/sysctl.d/99-security-hardening.conf 2>/dev/null || \ + print_warning "Certains paramètres sysctl ignorés" + + print_success "Paramètres sysctl appliqués" + mark_step_done "$step_name" +} + +configure_pam_password_policy() { + local step_name="configure_pam_password_policy" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Configuration de la politique de mots de passe PAM" + + backup_file "/etc/security/pwquality.conf" + + cat > /etc/security/pwquality.conf << EOF +# Politique de mot de passe stricte +minlen = 14 +minclass = 3 +maxrepeat = 3 +maxsequence = 3 +dcredit = -1 +ucredit = -1 +lcredit = -1 +ocredit = -1 +difok = 3 +maxclassrepeat = 3 +gecoscheck = 1 +EOF + + backup_file "/etc/pam.d/common-password" + + # Configuration PAM pour mots de passe forts + if grep -q "pam_pwquality.so" /etc/pam.d/common-password; then + sed -i 's/pam_pwquality.so.*/pam_pwquality.so retry=3 minlen=14 minclass=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root/' /etc/pam.d/common-password + else + sed -i '/^password.*pam_unix.so/ipassword requisite pam_pwquality.so retry=3 minlen=14 minclass=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root' /etc/pam.d/common-password + fi + + # Configuration hachage SHA512 avec rounds + sed -i 's/pam_unix.so.*/& sha512 rounds=500000 remember=5/' /etc/pam.d/common-password + + print_success "Politique PAM configurée" + mark_step_done "$step_name" +} + +configure_login_defs() { + local step_name="configure_login_defs" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Configuration des paramètres de connexion (login.defs)" + + backup_file "/etc/login.defs" + + # Mise à jour des valeurs avec variables auto + update_config_value "/etc/login.defs" "PASS_MAX_DAYS" "$AUTO_PASS_MAX_DAYS" + update_config_value "/etc/login.defs" "PASS_MIN_DAYS" "$AUTO_PASS_MIN_DAYS" + update_config_value "/etc/login.defs" "PASS_WARN_AGE" "7" + update_config_value "/etc/login.defs" "LOGIN_RETRIES" "3" + update_config_value "/etc/login.defs" "LOGIN_TIMEOUT" "60" + update_config_value "/etc/login.defs" "UMASK" "$AUTO_UMASK" + update_config_value "/etc/login.defs" "ENCRYPT_METHOD" "SHA512" + update_config_value "/etc/login.defs" "SHA_CRYPT_MIN_ROUNDS" "500000" + update_config_value "/etc/login.defs" "SHA_CRYPT_MAX_ROUNDS" "1000000" + + print_success "Configuration login.defs appliquée" + mark_step_done "$step_name" +} + +configure_umask() { + local step_name="configure_umask" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Configuration de l'umask par défaut" + + local umask_files="/etc/profile /etc/bash.bashrc /etc/zsh/zshrc" + + for file in $umask_files; do + [[ -f "$file" ]] && { + backup_file "$file" + sed -i '/^umask/d' "$file" + add_unique_line "umask $AUTO_UMASK" "$file" + } + done + + # Configuration pour les shells spécifiques + echo "umask $AUTO_UMASK" > /etc/profile.d/umask.sh + chmod 644 /etc/profile.d/umask.sh + + print_success "Umask configuré à $AUTO_UMASK" + mark_step_done "$step_name" +} + +harden_ssh() { + local step_name="harden_ssh" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Durcissement du service SSH" + + backup_file "/etc/ssh/sshd_config" + + local sshd_config="/etc/ssh/sshd_config" + local ssh_port=$(get_ssh_port_to_use) + + # Fonction utilitaire pour mettre à jour les paramètres SSH + update_ssh_param() { + local param="$1" + local value="$2" + sed -i "/^#*${param}[[:space:]]/d" "$sshd_config" + echo "$param $value" >> "$sshd_config" + } + + # Configuration de base + update_ssh_param "Port" "$ssh_port" + + # Si c'est un nouveau port et pas en LXC, garder le port 22 temporairement + if [[ "$ssh_port" != "22" ]] && ! detect_lxc; then + update_ssh_param "Port" "22" + update_ssh_param "Port" "$ssh_port" + print_info "Port 22 maintenu temporairement avec port $ssh_port" + fi + + # Authentification + if [[ "$AUTO_DISABLE_ROOT_LOGIN" == "yes" ]]; then + update_ssh_param "PermitRootLogin" "no" + print_info "Connexion root SSH désactivée" + else + update_ssh_param "PermitRootLogin" "prohibit-password" + print_info "Connexion root SSH autorisée uniquement par clé" + fi + + # Paramètres de sécurité + update_ssh_param "PasswordAuthentication" "no" + update_ssh_param "PubkeyAuthentication" "yes" + update_ssh_param "PermitEmptyPasswords" "no" + update_ssh_param "X11Forwarding" "no" + update_ssh_param "MaxAuthTries" "3" + update_ssh_param "ClientAliveInterval" "300" + update_ssh_param "ClientAliveCountMax" "2" + update_ssh_param "LoginGraceTime" "60" + update_ssh_param "MaxSessions" "2" + update_ssh_param "TCPKeepAlive" "no" + update_ssh_param "AllowAgentForwarding" "no" + update_ssh_param "AllowTcpForwarding" "no" + update_ssh_param "LogLevel" "VERBOSE" + update_ssh_param "Compression" "no" + update_ssh_param "Protocol" "2" + update_ssh_param "UseDNS" "no" + update_ssh_param "IgnoreRhosts" "yes" + update_ssh_param "HostbasedAuthentication" "no" + update_ssh_param "PrintLastLog" "yes" + update_ssh_param "StrictModes" "yes" + + # Algorithmes modernes + cat >> "$sshd_config" << 'EOF' +# Algorithmes de chiffrement modernes +Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr +MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com +KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 +HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com +EOF + + # Bannière + update_ssh_param "Banner" "/etc/issue.net" + + # Test et application + print_info "Test de la configuration SSH..." + if sshd -t 2>&1 | tee -a "$LOG_FILE"; then + print_success "Configuration SSH valide" + + print_info "Redémarrage du service SSH..." + if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then + print_success "SSH durci avec succès" + + if [[ "$ssh_port" != "22" ]] && ! detect_lxc; then + print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + print_warning "⚠ Port SSH: $ssh_port (22 temporairement actif)" + if [[ "$AUTO_CLEANUP_SSH" == "yes" ]] || auto_confirm; then + print_info "Nettoyage automatique du port 22 programmé" + else + print_warning "Testez: ssh -p $ssh_port $(whoami)@$(hostname -I | awk '{print $1}')" + fi + print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + fi + else + print_error "Échec redémarrage SSH - restauration..." + cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config + systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null + return 1 + fi + else + print_error "Configuration SSH invalide" + cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config + return 1 + fi + + mark_step_done "$step_name" +} + configure_firewall_ports() { local step_name="configure_firewall_ports" if check_step_done "$step_name"; then @@ -223,408 +983,573 @@ configure_firewall_ports() { return 0 fi - print_step "Configuration des règles de pare-feu basées sur les ports détectés" + print_step "Configuration des règles de pare-feu" - # Vérifier si on est dans un conteneur - if detect_container; then - print_warning "Conteneur détecté - pare-feu géré par l'hôte Proxmox" - print_info "UFW ne peut pas être configuré dans un conteneur LXC" - - # Désactiver UFW s'il est actif - if systemctl is-active --quiet ufw 2>/dev/null; then - ufw --force disable 2>/dev/null || true - systemctl stop ufw 2>/dev/null || true - systemctl disable ufw 2>/dev/null || true - systemctl mask ufw 2>/dev/null || true - print_info "Service UFW désactivé (non nécessaire en conteneur)" - fi - - print_success "Configuration pare-feu ignorée (environnement conteneurisé)" + if [[ "$AUTO_ENABLE_UFW" != "yes" ]]; then + print_info "UFW désactivé par configuration (AUTO_ENABLE_UFW=no)" mark_step_done "$step_name" return 0 fi - # Configuration pour système physique/VM - backup_file "/etc/ufw/user.rules" - backup_file "/etc/ufw/user6.rules" - - print_info "Réinitialisation des règles UFW..." - ufw --force reset > /dev/null 2>&1 || { - print_error "Impossible de réinitialiser UFW" + if detect_container; then + print_warning "Conteneur détecté - pare-feu géré par l'hôte" + + if command -v ufw > /dev/null 2>&1; then + ufw --force disable 2>/dev/null || true + systemctl stop ufw 2>/dev/null || true + systemctl disable ufw 2>/dev/null || true + print_info "Service UFW désactivé dans le conteneur" + fi + mark_step_done "$step_name" - return 1 - } + return 0 + fi - print_info "Configuration des politiques par défaut..." + # Arrêt et réinitialisation + ufw --force disable > /dev/null 2>&1 || true + ufw --force reset > /dev/null 2>&1 + + # Politiques par défaut ufw default deny incoming ufw default allow outgoing - # Déterminer le port SSH à utiliser + # Port SSH local ssh_port=$(get_ssh_port_to_use) + ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé' - # Autoriser le port SSH - print_info "Autorisation du port SSH $ssh_port..." - if ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé' > /dev/null 2>&1; then - print_info " ✓ Port SSH $ssh_port autorisé" - else - print_warning " ✗ Erreur avec le port SSH $ssh_port" - fi - - # Si on utilise un port différent de 22 et que le port 22 est ouvert, l'autoriser temporairement + # Si nouveau port, garder temporairement le port 22 if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then - print_info "Autorisation temporaire du port SSH 22 (à désactiver après test)..." - if ufw allow "22/tcp" comment 'SSH temporaire (à désactiver)' > /dev/null 2>&1; then - print_info " ✓ Port SSH 22 autorisé temporairement" - else - print_warning " ✗ Erreur avec le port SSH 22" - fi + ufw allow 22/tcp comment 'SSH temporaire' fi - # Définition des services courants avec leurs ports (version simplifiée) - declare -A service_ports=( - # Services web essentiels - ["HTTP"]="80" - ["HTTPS"]="443" - - # Services réseau de base - ["DNS"]="53" - ["DNS-UDP"]="53/udp" - ["NTP"]="123" - ["NTP-UDP"]="123/udp" - - # Email - ["SMTP"]="25" - ["SMTP-Submission"]="587" - ["SMTPS"]="465" - ["IMAP"]="143" - ["IMAPS"]="993" - ["POP3"]="110" - ["POP3S"]="995" - - # Base de données - ["MySQL"]="3306" - ["PostgreSQL"]="5432" - ["MongoDB"]="27017" - ["Redis"]="6379" - ["Elasticsearch"]="9200" - - # Monitoring & Logging - ["Graylog-Web"]="9000" - ["Graylog-API"]="12900" - ["Prometheus"]="9090" - ["Grafana"]="3000" - ["Node-Exporter"]="9100" - - # Services spécifiques demandés - ["Ollama"]="11434" - ["Gitea"]="3000" - ["Gitea-SSH"]="2222" - ["Bitwarden"]="80 443" - ["Teleport"]="3022 3023 3024 3025 3026" - ["NetBox"]="8000" - ["Wazuh"]="1514 1515 55000" - - # Services AD/Samba - ["Active-Directory"]="53 88 135 137 138 139 389 445 464 636 3268 3269" - ["Samba"]="137 138 139 445" - ["LDAP"]="389" - ["LDAPS"]="636" - ["Kerberos"]="88" - - # DevOps - ["Docker"]="2375 2376" - ["Kubernetes"]="6443 10250" - ["Jenkins"]="8080" - ["GitLab"]="80 443 22" - - # Virtualisation - ["Proxmox"]="8006" - ["VNC"]="5900" - ["RDP"]="3389" - ) - - print_info "Analyse des ports ouverts pour services connus..." - local services_authorized=0 - local total_services=${#service_ports[@]} - local current_service=0 - - # Parcourir tous les services - for service_name in "${!service_ports[@]}"; do - current_service=$((current_service + 1)) - local ports="${service_ports[$service_name]}" - local ports_array=($ports) - - # Vérifier chaque port du service - for port_entry in "${ports_array[@]}"; do - # Extraire le numéro de port (sans /udp ou /tcp) - local port_num=$(echo "$port_entry" | grep -o '^[0-9]*') - - # Vérifier si c'est une plage de ports - if [[ "$port_num" =~ ^[0-9]+-[0-9]+$ ]]; then - local start_port=$(echo "$port_num" | cut -d'-' -f1) - local end_port=$(echo "$port_num" | cut -d'-' -f2) - - # Vérifier si au moins un port de la plage est ouvert - local range_detected=false - for ((p=start_port; p<=end_port; p++)); do - if is_port_open "$p"; then - range_detected=true - break - fi - done - - if $range_detected; then - echo -e "${YELLOW}[$current_service/$total_services] Plage de ports détectée: $port_entry ($service_name)${NC}" - read -p " Autoriser la plage $start_port:$end_port ? (o/N): " -r confirm_range - - if [[ "$confirm_range" =~ ^[OoYy]$ ]]; then - # Déterminer le protocole - if [[ "$port_entry" == *"/udp" ]]; then - if ufw allow "$start_port:$end_port/udp" comment "$service_name" > /dev/null 2>&1; then - services_authorized=$((services_authorized + 1)) - print_info " ✓ Plage $start_port:$end_port/udp autorisée" - else - print_warning " ✗ Erreur avec la plage $port_entry" - fi - else - if ufw allow "$start_port:$end_port/tcp" comment "$service_name" > /dev/null 2>&1; then - services_authorized=$((services_authorized + 1)) - print_info " ✓ Plage $start_port:$end_port/tcp autorisée" - else - print_warning " ✗ Erreur avec la plage $port_entry" - fi - fi - else - print_info " ✗ Plage $port_entry non autorisée" - fi - fi - else - # Port unique - if is_port_open "$port_num"; then - echo -e "${YELLOW}[$current_service/$total_services] Port $port_entry détecté ($service_name)${NC}" - - # Demander confirmation - read -p " Autoriser ce port ? (o/N): " -r confirm_port - - if [[ "$confirm_port" =~ ^[OoYy]$ ]]; then - # Déterminer le protocole - if [[ "$port_entry" == *"/udp" ]]; then - if ufw allow "$port_num/udp" comment "$service_name" > /dev/null 2>&1; then - services_authorized=$((services_authorized + 1)) - print_info " ✓ Port $port_num/udp autorisé" - else - print_warning " ✗ Erreur avec le port $port_num/udp" - fi - else - # Par défaut, autoriser TCP - if ufw allow "$port_num/tcp" comment "$service_name" > /dev/null 2>&1; then - services_authorized=$((services_authorized + 1)) - print_info " ✓ Port $port_num/tcp autorisé" - else - print_warning " ✗ Erreur avec le port $port_num/tcp" - fi - - # Pour les ports non spécifiés, demander pour UDP aussi - if [[ ! "$port_entry" == *"/"* ]]; then - echo -n " Autoriser aussi en UDP ? (o/N): " - read -r confirm_udp - if [[ "$confirm_udp" =~ ^[OoYy]$ ]]; then - if ufw allow "$port_num/udp" comment "$service_name (UDP)" > /dev/null 2>&1; then - services_authorized=$((services_authorized + 1)) - print_info " ✓ Port $port_num/udp autorisé" - else - print_warning " ✗ Erreur avec le port $port_num/udp" - fi - fi - fi - fi - else - print_info " ✗ Port $port_entry non autorisé" - fi - fi - fi - done - done - - # Vérifier les ports restants non couverts - print_info "Recherche de ports ouverts supplémentaires..." - local unknown_ports_added=0 + # Ports essentiels (toujours autorisés) + ufw allow 53/udp comment 'DNS' + ufw allow 123/udp comment 'NTP' + # Ports détectés - autorisation automatique des services connus if [[ -f "$OPEN_PORTS_FILE" ]]; then - while IFS= read -r port; do - # Nettoyer le port - port=$(echo "$port" | tr -d '[:space:]') - [[ -z "$port" ]] && continue + while read -r port; do + [[ "$port" == "22" || "$port" == "$ssh_port" || "$port" == "53" || "$port" == "123" ]] && continue - # Ignorer les ports déjà traités - [[ "$port" == "22" || "$port" == "22022" || "$port" == "$ssh_port" ]] && continue - - # Vérifier si le port est déjà couvert par un service - local port_covered=false - for service_name in "${!service_ports[@]}"; do - local ports="${service_ports[$service_name]}" - for port_spec in $ports; do - local check_port=$(echo "$port_spec" | grep -o '^[0-9]*') - if [[ "$check_port" == "$port" ]]; then - port_covered=true - break 2 - fi - done - done - - if ! $port_covered; then - # Chercher une description dans /etc/services - local service_desc=$(grep -E "^[^#][^[:space:]]+[[:space:]]+$port/" /etc/services 2>/dev/null | awk '{print $1}' | head -1) - - if [[ -n "$service_desc" ]]; then - echo -e "${YELLOW} Port $port détecté ($service_desc)${NC}" - else - echo -e "${YELLOW} Port $port détecté (service inconnu)${NC}" - fi - - read -p " Autoriser ce port ? (o/N): " -r confirm_port - - if [[ "$confirm_port" =~ ^[OoYy]$ ]]; then - # Autoriser TCP - if ufw allow "${port}/tcp" comment "Port $port ${service_desc:+- $service_desc}" > /dev/null 2>&1; then - unknown_ports_added=$((unknown_ports_added + 1)) - print_info " ✓ Port $port/tcp autorisé" + # Vérifier si c'est un service connu + local service_name="" + case $port in + 80) service_name="HTTP" ;; + 443) service_name="HTTPS" ;; + 25) service_name="SMTP" ;; + 587) service_name="SMTP Submission" ;; + 465) service_name="SMTPS" ;; + 993) service_name="IMAPS" ;; + 995) service_name="POP3S" ;; + 3306) service_name="MySQL" ;; + 5432) service_name="PostgreSQL" ;; + 6379) service_name="Redis" ;; + 27017) service_name="MongoDB" ;; + 9200) service_name="Elasticsearch" ;; + 3000) service_name="Grafana/WebApp" ;; + 8080) service_name="Proxy/Web" ;; + 8443) service_name="HTTPS Alt" ;; + *) + # Pour les ports non standard, demander confirmation sauf en mode auto + if auto_confirm; then + ufw deny "${port}/tcp" comment "Port non standard $port (bloqué auto)" + print_info "Port $port/tcp bloqué automatiquement" else - print_warning " ✗ Erreur avec le port $port/tcp" - fi - - # Demander pour UDP - echo -n " Autoriser aussi en UDP ? (o/N): " - read -r confirm_udp - if [[ "$confirm_udp" =~ ^[OoYy]$ ]]; then - if ufw allow "${port}/udp" comment "Port $port/udp ${service_desc:+- $service_desc}" > /dev/null 2>&1; then - unknown_ports_added=$((unknown_ports_added + 1)) - print_info " ✓ Port $port/udp autorisé" + print_info "Port non standard détecté: $port" + read -p "Autoriser le port $port ? (o/N): " -r confirm_port + if [[ "$confirm_port" =~ ^[Oo]$ ]]; then + ufw allow "${port}/tcp" comment "Port personnalisé $port" + print_info "Port $port/tcp autorisé" else - print_warning " ✗ Erreur avec le port $port/udp" + ufw deny "${port}/tcp" comment "Port bloqué $port" + print_info "Port $port/tcp bloqué" fi fi - else - print_info " ✗ Port $port non autorisé" - fi + continue + ;; + esac + + if [[ -n "$service_name" ]]; then + ufw allow "${port}/tcp" comment "$service_name" + print_info "Port $port/tcp ($service_name) autorisé" fi done < "$OPEN_PORTS_FILE" - else - print_warning "Fichier des ports détectés non trouvé" fi - # Configuration ICMP (limitation des pings) - print_info "Configuration de la limitation ICMP..." - if [[ -f /etc/ufw/before.rules ]]; then - backup_file "/etc/ufw/before.rules" - - # Ajouter la limitation ICMP si elle n'existe pas déjà - if ! grep -q "Limiter pings" /etc/ufw/before.rules; then - # Chercher la ligne avant la fin du fichier - local insert_line=$(grep -n "^COMMIT" /etc/ufw/before.rules | head -1 | cut -d: -f1) - - if [[ -n "$insert_line" ]]; then - # Insérer avant COMMIT - sed -i "${insert_line}i# Limiter pings\n-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 3/second --limit-burst 5 -j ACCEPT\n-A ufw-before-input -p icmp --icmp-type echo-request -j DROP" /etc/ufw/before.rules - print_info " ✓ Limitation ICMP configurée" - else - # Ajouter à la fin - echo -e "\n# Limiter pings" >> /etc/ufw/before.rules - echo "-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 3/second --limit-burst 5 -j ACCEPT" >> /etc/ufw/before.rules - echo "-A ufw-before-input -p icmp --icmp-type echo-request -j DROP" >> /etc/ufw/before.rules - print_info " ✓ Limitation ICMP ajoutée" - fi - else - print_info " ⏭ Limitation ICMP déjà configurée" - fi - fi + # Activation + echo "y" | ufw --force enable > /dev/null 2>&1 - # Activer UFW - print_info "Activation du pare-feu UFW..." - - # Désactiver d'abord pour éviter les conflits - ufw --force disable > /dev/null 2>&1 || true - - # Activer avec confirmation automatique - if ufw --force enable > /dev/null 2>&1; then + # Vérification + if ufw status | grep -q "Status: active"; then print_success "Pare-feu UFW configuré et activé" - - # Afficher les statistiques - echo "" - echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${GREEN} RÉSUMÉ DE LA CONFIGURATION DU PAREFEU ${NC}" - echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo "" - echo -e "${BLUE}Services autorisés:${NC} $services_authorized" - echo -e "${BLUE}Ports personnalisés:${NC} $unknown_ports_added" - echo -e "${BLUE}Port SSH principal:${NC} $ssh_port" - if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then - echo -e "${YELLOW}Port SSH temporaire:${NC} 22 (à désactiver après test)" - fi - echo "" - - # Afficher le statut UFW - print_info "Statut UFW actuel:" - echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}" - ufw status verbose | head -20 - echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}" - + print_info "Règles appliquées:" + ufw status numbered | grep -E "ALLOW|DENY" else - # Fallback avec confirmation interactive - print_warning "Échec de l'activation automatique, tentative manuelle..." - if ufw enable <<< 'y' > /dev/null 2>&1; then - print_success "Pare-feu UFW activé (mode manuel)" - else - print_error "Échec critique de l'activation UFW" - print_info "Le pare-feu reste désactivé - vérifiez la configuration" - fi - fi - - # Sauvegarder la configuration - cat > "$BACKUP_DIR/firewall_config_$(date +%Y%m%d_%H%M%S).log" << EOF -=== CONFIGURATION DU PAREFEU === -Date: $(date) -Hostname: $(hostname) -Port SSH utilisé: $ssh_port - -Ports ouverts détectés: -$(cat "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun port détecté") - -Services autorisés: $services_authorized -Ports personnalisés: $unknown_ports_added - -Configuration UFW actuelle: -$(ufw status) - -=== FIN DE CONFIGURATION === -EOF - - print_info "Configuration sauvegardée dans: $BACKUP_DIR/firewall_config_*.log" - - # Instructions importantes - if [[ "$ssh_port" == "22022" ]]; then - echo "" - echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" - echo -e "${RED} ⚠ IMPORTANT ⚠ ${NC}" - echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" - echo "" - echo -e "${YELLOW}1. Testez immédiatement le nouveau port SSH:${NC}" - echo " ssh -p 22022 $(whoami)@$(hostname -I | awk '{print $1}')" - echo "" - echo -e "${YELLOW}2. Une fois confirmé, supprimez le port 22 avec:${NC}" - echo " $0 --cleanup-ssh" - echo "" - echo -e "${RED}NE FERMEZ PAS CETTE SESSION AVANT DE VALIDER SSH SUR LE PORT 22022${NC}" - echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" + print_warning "UFW activé avec avertissements" fi mark_step_done "$step_name" } -# ÉTAPE 3: Configuration Process Accounting +configure_fail2ban() { + local step_name="configure_fail2ban" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + if [[ "$AUTO_ENABLE_FAIL2BAN" != "yes" ]]; then + print_info "Fail2ban désactivé par configuration (AUTO_ENABLE_FAIL2BAN=no)" + mark_step_done "$step_name" + return 0 + fi + + print_step "Configuration de Fail2ban" + + backup_file "/etc/fail2ban/jail.conf" + + local ssh_port=$(get_ssh_port_to_use) + + # Configuration minimaliste pour mode auto + cat > /etc/fail2ban/jail.local << EOF +[DEFAULT] +bantime = 1h +findtime = 10m +maxretry = 3 +ignoreip = 127.0.0.1/8 ::1 +backend = auto +banaction = ufw +action = %(action_mwl)s + +[sshd] +enabled = true +port = $ssh_port +filter = sshd +logpath = /var/log/auth.log +maxretry = 5 +bantime = 2h + +[sshd-ddos] +enabled = true +port = $ssh_port +filter = sshd-ddos +logpath = /var/log/auth.log +maxretry = 10 +bantime = 1h + +[recidive] +enabled = true +filter = recidive +logpath = /var/log/fail2ban.log +action = iptables-allports[name=recidive] + sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log] +bantime = 1w +findtime = 1d +maxretry = 3 +EOF + + # Démarrer et activer + systemctl enable fail2ban 2>/dev/null || true + + if systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE"; then + print_success "Fail2ban configuré et démarré" + + # Vérification rapide + sleep 2 + if fail2ban-client status sshd 2>/dev/null | grep -q "Status for the jail"; then + print_info "Jail SSH actif sur le port $ssh_port" + fi + else + print_warning "Fail2ban démarré avec avertissements" + fi + + mark_step_done "$step_name" +} + +configure_aide_sha512() { + local step_name="configure_aide_sha512" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + if [[ "$AUTO_ENABLE_AIDE" != "yes" ]]; then + print_info "AIDE désactivé par configuration (AUTO_ENABLE_AIDE=no)" + mark_step_done "$step_name" + return 0 + fi + + print_step "Configuration AIDE pour SHA512" + + backup_file "/etc/aide/aide.conf" + + # Configuration simplifiée pour monitoring essentiel + cat > /etc/aide/aide.conf << 'EOF' +# Configuration AIDE simplifiée +@@define DBDIR /var/lib/aide +@@define LOGDIR /var/log/aide + +database=file:@@{DBDIR}/aide.db.gz +database_out=file:@@{DBDIR}/aide.db.new.gz +gzip_dbout=yes + +# Niveaux de vérification +Normal = sha512 +Large = sha512+ftype +Everything = sha512+ftype+p+i+n+u+g+s+m+c+acl+selinux+xattrs + +# Répertoires à surveiller +/etc Everything +/bin Large +/sbin Large +/usr/bin Large +/usr/sbin Large +/boot Large +/lib Large +/lib64 Large +/root Large + +# Exclusions +!/proc +!/sys +!/tmp +!/var/tmp +!/var/run +!/var/log +!/dev +EOF + + print_success "AIDE configuré pour SHA512" + mark_step_done "$step_name" +} + +initialize_aide_db() { + local step_name="initialize_aide_db" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + if [[ "$AUTO_ENABLE_AIDE" != "yes" ]]; then + mark_step_done "$step_name" + return 0 + fi + + print_step "Initialisation de la base de données AIDE" + + print_info "Création de la base de données de référence..." + + if aide --init --config /etc/aide/aide.conf 2>&1 | tee -a "$LOG_FILE"; then + if [[ -f /var/lib/aide/aide.db.new.gz ]]; then + mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz + print_success "Base de données AIDE initialisée" + else + print_warning "Fichier de base de données non trouvé" + fi + else + print_error "Échec initialisation AIDE" + return 1 + fi + + mark_step_done "$step_name" +} + +configure_chrony() { + local step_name="configure_chrony" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Configuration de la synchronisation horaire" + + if detect_container; then + print_warning "Conteneur détecté - NTP géré par l'hôte" + mark_step_done "$step_name" + return 0 + fi + + backup_file "/etc/chrony/chrony.conf" + + # Configuration optimisée pour la France + cat > /etc/chrony/chrony.conf << EOF +# Serveurs NTP français +pool 2.debian.pool.ntp.org iburst +server 0.fr.pool.ntp.org iburst +server 1.fr.pool.ntp.org iburst +server 2.fr.pool.ntp.org iburst +server 3.fr.pool.ntp.org iburst + +# Fichiers de travail +driftfile /var/lib/chrony/chrony.drift +logdir /var/log/chrony + +# Ajustements +makestep 1.0 3 +rtcsync + +# Restrictions +allow 127.0.0.1 +deny all + +# Stratum local +local stratum 10 +EOF + + # Définir le fuseau horaire + timedatectl set-timezone "$AUTO_TIMEZONE" 2>/dev/null || \ + print_warning "Impossible de définir le fuseau horaire $AUTO_TIMEZONE" + + # Redémarrer le service + systemctl restart chrony 2>/dev/null && \ + print_success "Chrony configuré" || \ + print_warning "Erreur démarrage Chrony" + + mark_step_done "$step_name" +} + +configure_automatic_updates() { + local step_name="configure_automatic_updates" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + print_step "Configuration des mises à jour automatiques" + + # Configuration unattended-upgrades + cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF' +Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}"; + "${distro_id}:${distro_codename}-security"; + "${distro_id}ESM:${distro_codename}"; +}; +Unattended-Upgrade::Origins-Pattern { + "origin=Debian,codename=${distro_codename},label=Debian-Security"; +}; +Unattended-Upgrade::AutoFixInterruptedDpkg "true"; +Unattended-Upgrade::MinimalSteps "true"; +Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; +Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Automatic-Reboot "false"; +Unattended-Upgrade::Automatic-Reboot-Time "04:00"; +EOF + + # Configuration périodique + cat > /etc/apt/apt.conf.d/10periodic << 'EOF' +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; +APT::Periodic::Unattended-Upgrade "1"; +EOF + + print_success "Mises à jour automatiques configurées" + mark_step_done "$step_name" +} + +configure_aide_cron() { + local step_name="configure_aide_cron" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + if [[ "$AUTO_ENABLE_AIDE" != "yes" ]]; then + mark_step_done "$step_name" + return 0 + fi + + print_step "Configuration des vérifications AIDE planifiées" + + # Tâche cron quotidienne + cat > /etc/cron.daily/aide-check << 'EOF' +#!/bin/bash +LOGFILE="/var/log/aide-check-$(date +%Y%m%d).log" + +if command -v aide > /dev/null; then + echo "=== Vérification AIDE $(date) ===" > "$LOGFILE" + + if /usr/bin/aide --check 2>&1 >> "$LOGFILE"; then + echo "AIDE: Vérification OK - $(date)" >> "$LOGFILE" + else + echo "AIDE: ALERTE - Changements détectés - $(date)" >> "$LOGFILE" + # Notification simple + echo "Alerte AIDE sur $(hostname)" | mail -s "[AIDE] Changements détectés" root 2>/dev/null || true + fi + + # Nettoyage des vieux logs (30 jours) + find /var/log -name "aide-check-*.log" -mtime +30 -delete 2>/dev/null || true +fi +EOF + + chmod 755 /etc/cron.daily/aide-check + + print_success "Vérification AIDE quotidienne configurée" + mark_step_done "$step_name" +} + +run_lynis_audit() { + local step_name="run_lynis_audit" + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi + + if [[ "$AUTO_SKIP_LYNIS" == "yes" ]]; then + print_info "Audit Lynis ignoré (AUTO_SKIP_LYNIS=yes)" + mark_step_done "$step_name" + return 0 + fi + + print_step "Exécution de l'audit Lynis" + + print_info "Audit système en cours..." + + lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1 + + # Extraire le score + local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1) + local max_score=100 + [[ -z "$score" ]] && score=0 + + # Afficher le score + local percentage=$((score * 100 / max_score)) + local bar_length=50 + local filled_length=$((percentage * bar_length / 100)) + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " SCORE LYNIS FINAL" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -n "Score: [" + for ((i=0; i /dev/null 2>&1 && ufw status | grep -q "22/tcp"; then + ufw delete allow 22/tcp 2>/dev/null || true + fi + + # Redémarrer SSH + if sshd -t && systemctl restart sshd 2>/dev/null; then + print_success "Port SSH 22 supprimé - SSH disponible uniquement sur le port $ssh_port" + else + print_error "Erreur configuration - restauration..." + cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config + systemctl restart sshd 2>/dev/null + return 1 + fi + else + print_warning "Nettoyage SSH non effectué (utilisez --cleanup-ssh ou AUTO_CLEANUP_SSH=yes)" + print_info "Ports SSH actifs: 22 (temporaire) et $ssh_port (permanent)" + fi +} + +# ============================================================================== +# FONCTIONS PRINCIPALES +# ============================================================================== + +check_requirements() { + print_info "Vérification des prérequis..." + + [[ $EUID -ne 0 ]] && { + print_error "Ce script doit être exécuté en tant que root" + exit 1 + } + + [[ ! -f /etc/debian_version ]] && { + print_warning "Système non Debian/Ubuntu - compatibilité limitée" + if ! auto_confirm; then + read -p "Continuer malgré tout ? (o/N): " -r confirm + [[ "$confirm" != "o" ]] && exit 1 + fi + } + + mkdir -p "$BACKUP_DIR" + touch "$LOG_FILE" "$STATUS_FILE" 2>/dev/null || { + print_error "Impossible de créer les fichiers de log" + exit 1 + } + + print_success "Prérequis vérifiés" +} + +print_header() { + clear + + echo -e "${CYAN}" + echo "╔══════════════════════════════════════════════════════════════════╗" + echo "║ SCRIPT DE DURCISSEMENT SYSTÈME OPTIMISÉ v8.1 ║" + echo "║ Sécurité Debian/Ubuntu ║" + echo "║ MODE AUTONOME ║" + echo "╚══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" + + echo -e "${BLUE}Informations système:${NC}" + echo " • Distribution: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"')" + echo " • Noyau: $(uname -r)" + echo " • Hostname: $(hostname)" + echo " • Type: $(detect_container && echo "Conteneur/LXC" || echo "Hôte physique/VM")" + echo "" + + echo -e "${BLUE}Configuration automatique:${NC}" + echo " • Port SSH: $AUTO_SSH_PORT" + echo " • Fuseau horaire: $AUTO_TIMEZONE" + echo " • Umask: $AUTO_UMASK" + echo " • Durée max mot de passe: $AUTO_PASS_MAX_DAYS jours" + echo " • Mode autonome: $( [[ "$UNATTENDED" == true ]] && echo "Activé" || echo "Désactivé" )" + echo "" + + echo -e "${YELLOW}Configuration des composants:${NC}" + echo " • Fail2ban: $( [[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && echo "✓" || echo "✗" )" + echo " • UFW: $( [[ "$AUTO_ENABLE_UFW" == "yes" ]] && echo "✓" || echo "✗" )" + echo " • AIDE: $( [[ "$AUTO_ENABLE_AIDE" == "yes" ]] && echo "✓" || echo "✗" )" + echo " • ClamAV: $( [[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && echo "✓" || echo "✗" )" + echo "" + + if ! auto_confirm; then + echo -e "${YELLOW}⚠ Ce script va modifier profondément la configuration système ⚠${NC}" + echo "" + read -p "Appuyez sur Entrée pour continuer ou Ctrl+C pour annuler..." + else + echo -e "${GREEN}Mode autonome activé - Démarrage automatique dans 3 secondes...${NC}" + sleep 3 + fi +} + configure_process_accounting() { local step_name="configure_process_accounting" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Configuration du Process Accounting" @@ -647,210 +1572,21 @@ configure_process_accounting() { mark_step_done "$step_name" } -# ÉTAPE 4: Durcissement sysctl -configure_sysctl_security() { - local step_name="configure_sysctl_security" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Durcissement des paramètres noyau (sysctl)" - - cat > /etc/sysctl.d/99-security-hardening.conf << 'EOF' -# Sécurité réseau IPv4 -net.ipv4.conf.all.accept_source_route = 0 -net.ipv4.conf.all.accept_redirects = 0 -net.ipv4.conf.all.send_redirects = 0 -net.ipv4.conf.all.log_martians = 1 -net.ipv4.conf.all.rp_filter = 1 -net.ipv4.conf.default.accept_source_route = 0 -net.ipv4.conf.default.accept_redirects = 0 -net.ipv4.conf.default.log_martians = 1 -net.ipv4.icmp_echo_ignore_broadcasts = 1 -net.ipv4.icmp_ignore_bogus_error_responses = 1 -net.ipv4.tcp_syncookies = 1 - -# Sécurité réseau IPv6 -net.ipv6.conf.all.accept_redirects = 0 -net.ipv6.conf.all.accept_source_route = 0 -net.ipv6.conf.default.accept_redirects = 0 -net.ipv6.conf.default.accept_source_route = 0 - -# Sécurité noyau -kernel.randomize_va_space = 2 -kernel.kptr_restrict = 2 -kernel.dmesg_restrict = 1 -kernel.yama.ptrace_scope = 1 -kernel.unprivileged_bpf_disabled = 1 - -# Sécurité système -fs.suid_dumpable = 0 -fs.protected_fifos = 2 -fs.protected_regular = 2 -fs.protected_symlinks = 1 -fs.protected_hardlinks = 1 -EOF - - sysctl -p /etc/sysctl.d/99-security-hardening.conf 2>/dev/null || \ - print_warning "Certains paramètres sysctl ignorés" - - print_success "Paramètres sysctl appliqués" - mark_step_done "$step_name" -} - -# ÉTAPE 5: Configuration permissions logs -configure_log_permissions() { - local step_name="configure_log_permissions" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration des permissions des fichiers de log" - - find /var/log -type f -exec chmod 640 {} \; 2>/dev/null || true - [[ -f /var/log/auth.log ]] && chmod 600 /var/log/auth.log - [[ -f /var/log/syslog ]] && chmod 600 /var/log/syslog - chown root:adm /var/log/ 2>/dev/null || true - chmod 750 /var/log/ 2>/dev/null || true - - print_success "Permissions des logs configurées" - mark_step_done "$step_name" -} - -# ÉTAPE 6: Politique mots de passe PAM -configure_pam_password_policy() { - local step_name="configure_pam_password_policy" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration de la politique de mots de passe PAM" - - backup_file "/etc/security/pwquality.conf" - - cat >> /etc/security/pwquality.conf << 'EOF' - -# Configuration renforcée -minlen = 14 -minclass = 3 -maxrepeat = 3 -maxsequence = 3 -dcredit = -1 -ucredit = -1 -lcredit = -1 -ocredit = -1 -difok = 3 -EOF - - backup_file "/etc/pam.d/common-password" - - # Ajouter remember et sha512 - sed -i 's/pam_unix.so.*/& remember=5 sha512 rounds=500000/' /etc/pam.d/common-password - - # Ajouter pwquality si absent - grep -q "pam_pwquality.so" /etc/pam.d/common-password || \ - sed -i '1i password requisite pam_pwquality.so retry=3' /etc/pam.d/common-password - - print_success "Politique PAM configurée" - mark_step_done "$step_name" -} - -# ÉTAPE 7: Configuration login.defs -configure_login_defs() { - local step_name="configure_login_defs" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration des paramètres de connexion (login.defs)" - - backup_file "/etc/login.defs" - - update_config_value "/etc/login.defs" "PASS_MAX_DAYS" "90" - update_config_value "/etc/login.defs" "PASS_MIN_DAYS" "7" - update_config_value "/etc/login.defs" "PASS_WARN_AGE" "7" - update_config_value "/etc/login.defs" "LOGIN_RETRIES" "3" - update_config_value "/etc/login.defs" "LOGIN_TIMEOUT" "60" - update_config_value "/etc/login.defs" "UMASK" "027" - update_config_value "/etc/login.defs" "ENCRYPT_METHOD" "SHA512" - update_config_value "/etc/login.defs" "SHA_CRYPT_MIN_ROUNDS" "500000" - update_config_value "/etc/login.defs" "SHA_CRYPT_MAX_ROUNDS" "1000000" - - print_success "Configuration login.defs appliquée" - mark_step_done "$step_name" -} - -# ÉTAPE 8: Configuration umask global -configure_umask() { - local step_name="configure_umask" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration de l'umask par défaut" - - for file in /etc/profile /etc/bash.bashrc; do - backup_file "$file" - sed -i '/^umask/d' "$file" - add_unique_line "umask 027" "$file" - done - - print_success "Umask configuré à 027" - mark_step_done "$step_name" -} - -# ÉTAPE 9: Configuration AIDE SHA512 -configure_aide_sha512() { - local step_name="configure_aide_sha512" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration AIDE pour SHA512" - - backup_file "/etc/aide/aide.conf" - - grep -q "ALLXTRAHASHES" /etc/aide/aide.conf && \ - sed -i 's/ALLXTRAHASHES.*=.*/ALLXTRAHASHES = sha512/' /etc/aide/aide.conf || \ - echo "ALLXTRAHASHES = sha512" >> /etc/aide/aide.conf - - print_success "AIDE configuré pour SHA512" - mark_step_done "$step_name" -} - -# ÉTAPE 10: Initialisation AIDE -initialize_aide_db() { - local step_name="initialize_aide_db" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Initialisation de la base de données AIDE" - - print_info "Création de la base de données de référence (peut prendre plusieurs minutes)..." - - if command -v aideinit > /dev/null; then - aideinit && print_success "Base de données AIDE initialisée via aideinit" || { - print_error "Échec aideinit" - return 1 - } - else - aide --init --config /etc/aide/aide.conf && { - [[ -f /var/lib/aide/aide.db.new ]] && { - mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db - print_success "Base de données AIDE initialisée" - } || { - print_error "Fichier de base de données non trouvé" - return 1 - } - } || { - print_error "Échec initialisation AIDE" - return 1 - } - fi - - mark_step_done "$step_name" -} - -# ÉTAPE 11: Configuration ClamAV configure_clamav() { local step_name="configure_clamav" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Configuration de ClamAV" print_info "Mise à jour de la base de données ClamAV..." systemctl stop clamav-freshclam 2>/dev/null || true - freshclam || print_warning "Échec mise à jour ClamAV (normal si déjà récent)" + freshclam --quiet 2>/dev/null || print_warning "Échec mise à jour ClamAV" - systemctl enable clamav-freshclam - systemctl start clamav-freshclam + systemctl enable clamav-freshclam 2>/dev/null || true + systemctl start clamav-freshclam 2>/dev/null || true systemctl enable clamav-daemon 2>/dev/null || true systemctl start clamav-daemon 2>/dev/null || true @@ -858,184 +1594,12 @@ configure_clamav() { mark_step_done "$step_name" } -# ÉTAPE 12: Configuration Chrony -configure_chrony() { - local step_name="configure_chrony" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration de la synchronisation horaire (Chrony)" - - # Vérifier si on est dans un conteneur - if detect_container; then - print_warning "Conteneur détecté - synchronisation horaire gérée par l'hôte" - print_info "La configuration de Chrony est ignorée en environnement conteneurisé" - - # Désactiver/masquer chrony s'il est présent - if systemctl list-unit-files | grep -q "^chrony.service"; then - systemctl stop chrony 2>/dev/null || true - systemctl disable chrony 2>/dev/null || true - systemctl mask chrony 2>/dev/null || true - print_info "Service Chrony désactivé (non nécessaire en conteneur)" - fi - - mark_step_done "$step_name" - return 0 - fi - - # Configuration pour système physique/VM - backup_file "/etc/chrony/chrony.conf" - - # Configurer le fuseau horaire - timedatectl set-timezone Europe/Paris || { - print_warning "Impossible de définir le fuseau horaire" - } - - cat > /etc/chrony/chrony.conf << 'EOF' -# Serveurs NTP -pool 2.debian.pool.ntp.org iburst -server 0.fr.pool.ntp.org iburst -server 1.fr.pool.ntp.org iburst - -# Fichiers de drift et log -driftfile /var/lib/chrony/chrony.drift -logdir /var/log/chrony - -# Restrictions -makestep 1.0 3 -rtcsync -EOF - - # Activer et démarrer le service - if systemctl enable chrony 2>/dev/null && systemctl restart chrony 2>/dev/null; then - print_success "Chrony configuré et démarré" - else - print_warning "Erreur lors du démarrage de Chrony - service peut-être non disponible" - fi - - mark_step_done "$step_name" -} - -# ÉTAPE 13: Durcissement SSH (adapté pour LXC) -harden_ssh() { - local step_name="harden_ssh" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Durcissement du service SSH" - - backup_file "/etc/ssh/sshd_config" - - local sshd_config="/etc/ssh/sshd_config" - - # Fonction helper pour mettre à jour une valeur - update_ssh_param() { - local param="$1" - local value="$2" - local file="$3" - - # Supprimer toutes les occurrences (commentées ou non) - sed -i "/^#*${param}/d" "$file" - # Ajouter la nouvelle valeur - echo "${param} ${value}" >> "$file" - } - - # Déterminer le port SSH à utiliser - local ssh_port=$(get_ssh_port_to_use) - - # Configuration des ports - sed -i '/^Port /d' "$sshd_config" - sed -i '/^#Port /d' "$sshd_config" - echo "Port $ssh_port" >> "$sshd_config" - - # Si LXC et port 22022 non ouvert, garder aussi le port 22 - if detect_lxc && [[ "$ssh_port" == "22" ]]; then - print_info "LXC détecté - port 22 maintenu (port 22022 non disponible)" - elif [[ "$ssh_port" == "22022" ]]; then - # Pour les systèmes non-LXC ou LXC avec port 22022 ouvert, ajouter le port 22 temporairement - echo "Port 22" >> "$sshd_config" - print_info "Port 22 ajouté temporairement (à désactiver après test)" - fi - - # Paramètres de sécurité avec la fonction helper - update_ssh_param "PermitRootLogin" "yes" "$sshd_config" - update_ssh_param "PasswordAuthentication" "no" "$sshd_config" - update_ssh_param "PubkeyAuthentication" "yes" "$sshd_config" - update_ssh_param "PermitEmptyPasswords" "no" "$sshd_config" - update_ssh_param "X11Forwarding" "no" "$sshd_config" - update_ssh_param "MaxAuthTries" "3" "$sshd_config" - update_ssh_param "ClientAliveInterval" "300" "$sshd_config" - update_ssh_param "ClientAliveCountMax" "2" "$sshd_config" - update_ssh_param "LoginGraceTime" "60" "$sshd_config" - update_ssh_param "MaxSessions" "2" "$sshd_config" - update_ssh_param "TCPKeepAlive" "no" "$sshd_config" - update_ssh_param "AllowAgentForwarding" "no" "$sshd_config" - update_ssh_param "AllowTcpForwarding" "no" "$sshd_config" - update_ssh_param "LogLevel" "VERBOSE" "$sshd_config" - update_ssh_param "Compression" "no" "$sshd_config" - update_ssh_param "Protocol" "2" "$sshd_config" - - # Algorithmes sécurisés (s'assurer qu'ils n'existent pas déjà) - sed -i '/^Ciphers /d' "$sshd_config" - sed -i '/^MACs /d' "$sshd_config" - sed -i '/^KexAlgorithms /d' "$sshd_config" - - cat >> "$sshd_config" << 'EOF' - -# Algorithmes cryptographiques sécurisés -Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256 -KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 -EOF - - # Bannière - update_ssh_param "Banner" "/etc/issue.net" "$sshd_config" - - # Test de la configuration avant application - print_info "Test de la configuration SSH..." - if sshd -t 2>&1 | tee -a "$LOG_FILE"; then - print_success "Configuration SSH valide" - - # Redémarrer le service - print_info "Redémarrage du service SSH..." - if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then - print_success "SSH durci avec succès" - - # Afficher les ports configurés - local ports=$(grep "^Port" "$sshd_config" | awk '{print $2}' | tr '\n' ' ') - print_info "Ports SSH actifs: $ports" - - if [[ "$ssh_port" == "22022" ]]; then - print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_warning "⚠ TESTEZ IMMÉDIATEMENT dans un NOUVEAU terminal:" - print_warning " ssh -p $ssh_port $(whoami)@$(hostname -I | awk '{print $1}')" - print_warning " NE FERMEZ PAS cette session avant validation!" - print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - elif detect_lxc; then - print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_warning "⚠ Conteneur LXC - Port SSH: 22" - print_warning " Port 22022 non disponible dans ce conteneur" - print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - fi - else - print_error "Échec redémarrage SSH" - print_error "Restauration de la configuration..." - cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config - systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null - return 1 - fi - else - print_error "Configuration SSH invalide - vérifiez les logs" - print_error "Restauration de la sauvegarde..." - cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config - return 1 - fi - - mark_step_done "$step_name" -} - -# ÉTAPE 14: Configuration bannières configure_banners() { local step_name="configure_banners" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Configuration des bannières légales" @@ -1055,98 +1619,12 @@ configure_banners() { mark_step_done "$step_name" } -# ÉTAPE 15: Configuration pare-feu basée sur ports détectés (déjà définie) - -# ÉTAPE 16: Configuration Fail2ban -configure_fail2ban() { - local step_name="configure_fail2ban" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration de Fail2ban" - - backup_file "/etc/fail2ban/jail.conf" - - # Déterminer le port SSH - local ssh_port=$(get_ssh_port_to_use) - - # Configuration adaptée selon l'environnement - if detect_container; then - print_warning "Conteneur détecté - configuration Fail2ban limitée" - - cat > /etc/fail2ban/jail.local << EOF -[DEFAULT] -bantime = 1h -findtime = 10m -maxretry = 3 -ignoreip = 127.0.0.1/8 ::1 -backend = systemd - -# Mode conteneur - pas d'action iptables -banaction = %(banaction_allports)s -banaction_allports = iptables-multiport - -[sshd] -enabled = true -port = $ssh_port -filter = sshd -logpath = /var/log/auth.log -maxretry = 5 -bantime = 1h -# Action désactivée en conteneur (pas d'accès iptables) -action = %(action_)s -EOF - print_info "Fail2ban configuré en mode conteneur (logging seul, sans bannissement iptables)" - else - # Configuration complète pour système physique/VM - cat > /etc/fail2ban/jail.local << EOF -[DEFAULT] -bantime = 1h -findtime = 10m -maxretry = 3 -ignoreip = 127.0.0.1/8 ::1 -backend = systemd - -[sshd] -enabled = true -port = $ssh_port -filter = sshd -logpath = /var/log/auth.log -maxretry = 5 -bantime = 1h - -[apache-auth] -enabled = true -port = http,https -logpath = /var/log/apache*/*error.log -maxretry = 3 -EOF - print_info "Fail2ban configuré en mode complet avec bannissement iptables" - fi - - # Activer et démarrer le service - systemctl enable fail2ban 2>&1 | tee -a "$LOG_FILE" || true - - if systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE"; then - print_success "Fail2ban configuré et démarré" - - # Vérifier le statut - sleep 2 - if systemctl is-active --quiet fail2ban; then - print_info "Statut Fail2ban: $(systemctl is-active fail2ban)" - else - print_warning "Fail2ban démarré mais statut incertain" - fi - else - print_warning "Problème au démarrage de Fail2ban - vérifiez les logs: journalctl -xeu fail2ban" - fi - - mark_step_done "$step_name" -} - -# ÉTAPE 17: Suppression paquets inutiles remove_unneeded_packages() { local step_name="remove_unneeded_packages" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Suppression des paquets inutiles" @@ -1165,10 +1643,12 @@ remove_unneeded_packages() { mark_step_done "$step_name" } -# ÉTAPE 18: Restriction permissions restrict_file_permissions() { local step_name="restrict_file_permissions" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Restriction des permissions des fichiers critiques" @@ -1186,15 +1666,16 @@ restrict_file_permissions() { mark_step_done "$step_name" } -# ÉTAPE 19: Désactivation modules noyau disable_risky_kernel_modules() { local step_name="disable_risky_kernel_modules" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Désactivation des modules noyau risqués" cat > /etc/modprobe.d/hardening-blacklist.conf << 'EOF' -# Protocoles réseau non utilisés blacklist dccp install dccp /bin/true blacklist sctp @@ -1203,8 +1684,6 @@ blacklist rds install rds /bin/true blacklist tipc install tipc /bin/true - -# Systèmes de fichiers non utilisés blacklist cramfs install cramfs /bin/true blacklist freevxfs @@ -1219,12 +1698,8 @@ blacklist squashfs install squashfs /bin/true blacklist udf install udf /bin/true - -# FireWire (DMA attack) blacklist firewire-core install firewire-core /bin/true - -# Thunderbolt (DMA attack) blacklist thunderbolt install thunderbolt /bin/true EOF @@ -1237,25 +1712,21 @@ EOF mark_step_done "$step_name" } -# ÉTAPE 20: Configuration limites sécurité configure_security_limits() { local step_name="configure_security_limits" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Configuration des limites de sécurité" backup_file "/etc/security/limits.conf" cat >> /etc/security/limits.conf << 'EOF' - -# Désactiver core dumps * hard core 0 - -# Limiter les processus * soft nproc 512 * hard nproc 1024 - -# Limiter les fichiers ouverts * soft nofile 65536 * hard nofile 65536 EOF @@ -1264,93 +1735,28 @@ EOF mark_step_done "$step_name" } -# ÉTAPE 21: Vérification intégrité paquets verify_packages_integrity() { local step_name="verify_packages_integrity" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Vérification de l'intégrité des paquets" - print_info "Vérification avec debsums (peut prendre du temps)..." + print_info "Vérification avec debsums..." debsums -s 2>&1 | tee -a "$LOG_FILE" || print_warning "Certains paquets ont des erreurs" print_success "Vérification d'intégrité terminée" mark_step_done "$step_name" } -# ÉTAPE 22: Configuration mises à jour auto -configure_automatic_updates() { - local step_name="configure_automatic_updates" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration des mises à jour automatiques" - - cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF' -Unattended-Upgrade::Allowed-Origins { - "${distro_id}:${distro_codename}-security"; - "${distro_id}:${distro_codename}-updates"; -}; - -Unattended-Upgrade::AutoFixInterruptedDpkg "true"; -Unattended-Upgrade::MinimalSteps "true"; -Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; -Unattended-Upgrade::Remove-Unused-Dependencies "true"; -Unattended-Upgrade::Automatic-Reboot "false"; -Unattended-Upgrade::Automatic-Reboot-Time "04:00"; -Unattended-Upgrade::Mail "root"; -Unattended-Upgrade::MailReport "on-change"; -EOF - - cat > /etc/apt/apt.conf.d/10periodic << 'EOF' -APT::Periodic::Update-Package-Lists "1"; -APT::Periodic::Download-Upgradeable-Packages "1"; -APT::Periodic::AutocleanInterval "7"; -APT::Periodic::Unattended-Upgrade "1"; -EOF - - print_success "Mises à jour automatiques configurées" - mark_step_done "$step_name" -} - -# ÉTAPE 23: Configuration tâche AIDE cron -configure_aide_cron() { - local step_name="configure_aide_cron" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Configuration des vérifications AIDE planifiées" - - cat > /etc/cron.weekly/aide-check << 'EOF' -#!/bin/bash -LOGFILE="/var/log/aide-check-$(date +%Y%m%d).log" - -if command -v aide > /dev/null; then - echo "=== Vérification AIDE $(date) ===" > "$LOGFILE" - - if /usr/bin/aide --check >> "$LOGFILE" 2>&1; then - echo "Vérification réussie" >> "$LOGFILE" - - if grep -q "added\|changed\|removed" "$LOGFILE"; then - mail -s "[AIDE] Changements détectés sur $(hostname)" root < "$LOGFILE" - fi - else - echo "Échec vérification" >> "$LOGFILE" - mail -s "[AIDE] ALERTE - Échec sur $(hostname)" root < "$LOGFILE" - fi - - echo "=== Fin vérification $(date) ===" >> "$LOGFILE" -fi -EOF - - chmod 750 /etc/cron.weekly/aide-check - - print_success "Vérification AIDE hebdomadaire configurée" - mark_step_done "$step_name" -} - -# ÉTAPE 24: Durcissement bannière SMTP harden_smtp_banner() { local step_name="harden_smtp_banner" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Durcissement bannière SMTP" @@ -1373,10 +1779,12 @@ harden_smtp_banner() { mark_step_done "$step_name" } -# ÉTAPE 25: Durcissement services systemd harden_systemd_services() { local step_name="harden_systemd_services" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Durcissement services systemd" @@ -1386,13 +1794,9 @@ harden_systemd_services() { return 0 } - # Vérifier si on est dans un conteneur if detect_container; then print_warning "Conteneur détecté - durcissement systemd limité" - print_info "Les namespaces systemd ne sont pas supportés dans les conteneurs LXC" - print_info "Le durcissement systemd est ignoré pour éviter les erreurs de démarrage" - # Nettoyer les éventuelles configurations précédentes qui pourraient causer des problèmes local services=(ssh sshd fail2ban chrony) for service in "${services[@]}"; do local unit="${service}.service" @@ -1405,19 +1809,17 @@ harden_systemd_services() { done systemctl daemon-reload - print_success "Configuration systemd nettoyée pour compatibilité conteneur" + print_success "Configuration systemd nettoyée" mark_step_done "$step_name" return 0 fi - # Configuration pour système physique/VM local services=(ssh sshd fail2ban chrony) local hardened=0 for service in "${services[@]}"; do local unit="${service}.service" - # Vérifier si le service existe et est actif ou enabled systemctl list-unit-files | grep -q "^${unit}" || continue if ! systemctl is-enabled "$unit" > /dev/null 2>&1 && \ @@ -1428,9 +1830,7 @@ harden_systemd_services() { mkdir -p "/etc/systemd/system/${unit}.d" - # Configuration de sécurité adaptée selon le service if [[ "$service" == "ssh" || "$service" == "sshd" ]]; then - # Configuration plus permissive pour SSH cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF [Service] NoNewPrivileges=yes @@ -1443,7 +1843,6 @@ ProtectControlGroups=yes RestrictRealtime=yes EOF else - # Configuration standard pour les autres services cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF [Service] NoNewPrivileges=yes @@ -1463,10 +1862,8 @@ EOF print_info "Service $unit durci" done - # Recharger la configuration systemd systemctl daemon-reload - # Vérifier que les services critiques démarrent toujours print_info "Vérification des services critiques..." for service in ssh sshd; do local unit="${service}.service" @@ -1486,16 +1883,17 @@ EOF mark_step_done "$step_name" } -# ÉTAPE 26: Configuration PAM avancée configure_advanced_pam() { local step_name="configure_advanced_pam" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Configuration PAM avancée" backup_file "/etc/pam.d/common-password" - # S'assurer sha512 et rounds grep -q "pam_unix.so" /etc/pam.d/common-password && { grep -q "sha512" /etc/pam.d/common-password || \ sed -i 's/pam_unix.so.*/& sha512/' /etc/pam.d/common-password @@ -1504,7 +1902,6 @@ configure_advanced_pam() { sed -i 's/pam_unix.so.*/& rounds=500000/' /etc/pam.d/common-password } - # Appliquer expiration mots de passe print_info "Application expiration mots de passe aux utilisateurs..." while IFS=: read -r user _ uid _ _ _ shell; do [[ "$uid" -ge 1000 || "$user" == "root" ]] && \ @@ -1521,10 +1918,12 @@ configure_advanced_pam() { mark_step_done "$step_name" } -# ÉTAPE 27: Vérification partitions check_partition_layout() { local step_name="check_partition_layout" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Vérification disposition partitions" @@ -1551,10 +1950,12 @@ check_partition_layout() { mark_step_done "$step_name" } -# ÉTAPE 28: Vérification fichiers noyau check_vmlinuz() { local step_name="check_vmlinuz" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Vérification fichiers noyau" @@ -1581,29 +1982,32 @@ check_vmlinuz() { mark_step_done "$step_name" } -# ÉTAPE 29: Exécution chkrootkit run_chkrootkit() { local step_name="run_chkrootkit" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Exécution de chkrootkit" print_info "Scan rootkit en cours..." chkrootkit > "$BACKUP_DIR/chkrootkit_report.log" 2>&1 || \ - print_warning "Chkrootkit a détecté des avertissements (voir $BACKUP_DIR/chkrootkit_report.log)" + print_warning "Chkrootkit a détecté des avertissements" print_success "Scan chkrootkit terminé" mark_step_done "$step_name" } -# ÉTAPE 30: Nettoyage port SSH 22 temporaire prepare_ssh_cleanup() { local step_name="prepare_ssh_cleanup" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } + if check_step_done "$step_name"; then + skip_step "${STEP_DESCRIPTIONS[$step_name]}" + return 0 + fi print_step "Préparation nettoyage port SSH 22" - # Vérifier si nous avons besoin de nettoyer le port 22 local ssh_port=$(get_ssh_port_to_use) if [[ "$ssh_port" == "22022" ]]; then @@ -1618,218 +2022,6 @@ prepare_ssh_cleanup() { mark_step_done "$step_name" } -# ÉTAPE 31: Audit Lynis final -run_lynis_audit() { - local step_name="run_lynis_audit" - check_step_done "$step_name" && { skip_step "$step_name"; return 0; } - - print_step "Exécution de l'audit Lynis" - - print_info "Audit système complet en cours (peut prendre plusieurs minutes)..." - - # Exécuter Lynis et sauvegarder le rapport - lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1 - - # Extraire le score de durcissement - local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1) - local max_score=100 # Score maximum standard Lynis - - # Si on ne trouve pas le score dans le rapport, utiliser une valeur par défaut - [[ -z "$score" ]] && score=0 - - # Sauvegarder le score pour le résumé - echo "$score" > "/tmp/lynis_score.txt" - echo "$max_score" > "/tmp/lynis_max_score.txt" - - # Calculer le pourcentage pour la barre de progression - local percentage=$((score * 100 / max_score)) - local bar_length=50 # Longueur de la barre en caractères - local filled_length=$((percentage * bar_length / 100)) - local empty_length=$((bar_length - filled_length)) - - # Créer la barre de progression - local bar="[" - for ((i=0; i/dev/null || echo "0") - local suggestions=$(grep -c "Suggestion" "$SECURITY_REPORT" 2>/dev/null || echo "0") - local tests=$(grep -c "Test" "$SECURITY_REPORT" 2>/dev/null || echo "0") - - echo " • Tests effectués: $tests" - echo " • Avertissements: $warnings" - echo " • Suggestions: $suggestions" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - # Afficher les principales suggestions si score < 80 - if [[ $score -lt 80 ]]; then - echo "" - print_info "Top 5 des suggestions prioritaires:" - echo "┌──────────────────────────────────────────────────────┐" - grep "Suggestion" "$SECURITY_REPORT" | head -5 | nl -w1 -s'. ' | while read -r line; do - echo "│ ${line}" - done - echo "└──────────────────────────────────────────────────────┘" - fi - - # Afficher les warnings critiques - echo "" - if [[ $warnings -gt 0 ]]; then - print_warning "⚠ Avertissements critiques détectés:" - echo "┌──────────────────────────────────────────────────────┐" - grep "Warning" "$SECURITY_REPORT" | head -3 | nl -w1 -s'. ' | while read -r line; do - echo "│ ${line}" - done - echo "└──────────────────────────────────────────────────────┘" - fi - - echo "" - print_info "Pour voir le rapport complet: cat $SECURITY_REPORT" - print_info "Pour voir les suggestions: grep Suggestion $SECURITY_REPORT" - - mark_step_done "$step_name" -} - -# ============================================================================== -# FONCTION NETTOYAGE SSH -# ============================================================================== - -cleanup_ssh_port() { - print_step "Nettoyage définitif port SSH 22" - - echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}" - echo -e "${RED}║ ⚠ ATTENTION CRITIQUE ⚠ ║${NC}" - echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}" - echo "" - - # Vérifier si on est dans un conteneur LXC avec port 22 seulement - if detect_lxc && ! is_port_open "22022"; then - print_warning "Conteneur LXC détecté - port 22022 non disponible" - print_info "Le port 22 restera actif dans ce conteneur" - echo "" - echo -n "Continuer malgré tout ? (oui/non): " - read -r confirmation - [[ "$confirmation" != "oui" ]] && { - print_info "Annulation - port 22 maintenu" - return 0 - } - fi - - echo "Cette action va supprimer définitivement l'accès SSH sur le port 22." - echo "Assurez-vous d'avoir testé SSH sur le port $NEW_SSH_PORT." - echo "" - echo -n "Confirmez-vous avoir testé avec succès SSH:$NEW_SSH_PORT ? (oui/non): " - - read -r confirmation - - [[ "$confirmation" != "oui" ]] && { - print_error "Annulation. Testez d'abord: ssh -p $NEW_SSH_PORT user@$(hostname -I | awk '{print $1}')" - return 1 - } - - backup_file "/etc/ssh/sshd_config" - sed -i '/^Port 22$/d' /etc/ssh/sshd_config - - # Supprimer la règle UFW seulement si UFW est actif - if systemctl is-active --quiet ufw 2>/dev/null; then - ufw delete allow 22/tcp 2>/dev/null || true - fi - - sshd -t && { - systemctl restart sshd - print_success "Port SSH 22 supprimé" - print_info "SSH disponible uniquement sur le port $NEW_SSH_PORT" - } || { - print_error "Erreur configuration - restauration" - cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config - systemctl restart sshd - return 1 - } -} - -# ============================================================================== -# FONCTIONS PRINCIPALES -# ============================================================================== - -check_requirements() { - print_info "Vérification des prérequis..." - - [[ $EUID -ne 0 ]] && { - print_error "Ce script doit être exécuté en tant que root" - exit 1 - } - - [[ ! -f /etc/debian_version ]] && \ - print_warning "Système non Debian/Ubuntu - compatibilité limitée" - - mkdir -p "$BACKUP_DIR" - touch "$LOG_FILE" "$STATUS_FILE" 2>/dev/null || { - print_error "Impossible de créer les fichiers de log" - exit 1 - } - - print_success "Prérequis vérifiés" -} - -print_header() { - clear - - echo -e "${CYAN}" - echo "╔══════════════════════════════════════════════════════════════════╗" - echo "║ SCRIPT DE DURCISSEMENT SYSTÈME OPTIMISÉ v7.0 ║" - echo "║ Sécurité Debian/Ubuntu ║" - echo "║ avec détection automatique des ports ║" - echo "╚══════════════════════════════════════════════════════════════════╝" - echo -e "${NC}" - - echo -e "${BLUE}Informations système:${NC}" - echo " • Distribution: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"')" - echo " • Noyau: $(uname -r)" - echo " • Hostname: $(hostname)" - echo " • Type: $(detect_container && echo "Conteneur/LXC" || echo "Hôte physique/VM")" - echo "" - echo -e "${BLUE}Configuration:${NC}" - echo " • Port SSH nouveau: $NEW_SSH_PORT" - echo " • Détection ports: Activée" - echo " • Fichier log: $LOG_FILE" - echo " • Sauvegardes: $BACKUP_DIR" - echo " • Rapport final: $SECURITY_REPORT" - echo "" - - echo -e "${YELLOW}⚠ Ce script va modifier profondément la configuration système ⚠${NC}" - echo "" - - read -p "Appuyez sur Entrée pour continuer ou Ctrl+C pour annuler..." -} - print_summary() { echo -e "\n${GREEN}" echo "╔══════════════════════════════════════════════════════════════════╗" @@ -1837,206 +2029,82 @@ print_summary() { echo "╚══════════════════════════════════════════════════════════════════╝" echo -e "${NC}" - # Récupérer le score Lynis - local lynis_score="N/A" - local lynis_max_score=100 - - if [[ -f "/tmp/lynis_score.txt" ]]; then - lynis_score=$(cat /tmp/lynis_score.txt) - [[ -f "/tmp/lynis_max_score.txt" ]] && lynis_max_score=$(cat /tmp/lynis_max_score.txt) - elif [[ -f "$SECURITY_REPORT" ]]; then - lynis_score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1) - [[ -z "$lynis_score" ]] && lynis_score="N/A" - fi - - # Afficher le score avec barre de progression dans le résumé - if [[ "$lynis_score" != "N/A" ]]; then - # Calcul de la barre de progression - local percentage=$((lynis_score * 100 / lynis_max_score)) - local bar_length=25 # Barre plus courte pour le résumé - local filled_length=$((percentage * bar_length / 100)) - local empty_length=$((bar_length - filled_length)) - - local bar="[" - for ((i=0; i/dev/null || echo "désactivé (conteneur)")" - echo " • Fail2ban: $(systemctl is-active fail2ban 2>/dev/null || echo "inactif")" - echo " • AIDE: Base initialisée + vérification hebdomadaire" - echo " • ClamAV: $(systemctl is-active clamav-freshclam 2>/dev/null || echo "inactif")" - echo " • Chrony: $(systemctl is-active chrony 2>/dev/null || echo "inactif (conteneur)")" - echo "" - - # Afficher les ports détectés et configurés - if [[ -f "$OPEN_PORTS_FILE" ]]; then - echo -e "${YELLOW}🌐 PORTS DÉTECTÉS ET CONFIGURÉS:${NC}" - echo " • Ports ouverts détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun")" - if systemctl is-active --quiet ufw 2>/dev/null; then - echo " • Règles UFW appliquées: $(ufw status | grep -c 'ALLOW') règle(s)" - fi - echo "" + if [[ -f "$SECURITY_REPORT" ]]; then + local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1) + [[ -n "$score" ]] && { + echo -e "${YELLOW}📊 SCORE DE SÉCURITÉ:${NC}" + echo " • Score Lynis: $score/100" + echo "" + } fi echo -e "${YELLOW}📁 FICHIERS GÉNÉRÉS:${NC}" echo " • Log complet: $LOG_FILE" echo " • Sauvegardes: $BACKUP_DIR" echo " • Rapport Lynis: $SECURITY_REPORT" - echo " • Rapport chkrootkit: $BACKUP_DIR/chkrootkit_report.log" - echo " • Ports détectés: $OPEN_PORTS_FILE" echo "" - # Statistiques Lynis détaillées - if [[ -f "$SECURITY_REPORT" ]]; then - local warnings=$(grep -c "Warning" "$SECURITY_REPORT" 2>/dev/null || echo "0") - local suggestions=$(grep -c "Suggestion" "$SECURITY_REPORT" 2>/dev/null || echo "0") - - echo -e "${YELLOW}📈 STATISTIQUES LYNIS:${NC}" - echo " • Avertissements: $warnings" - echo " • Suggestions d'amélioration: $suggestions" - echo "" - fi - - detect_container && { - echo -e "${YELLOW}📦 SPÉCIFICITÉS CONTENEUR:${NC}" - echo " • Process Accounting: Désactivé (géré par hôte)" - echo " • UFW/Pare-feu: Géré par l'hôte Proxmox" - echo " • Modules noyau: Gérés par l'hôte" - echo " • Partitions: Gérées par Proxmox" - echo " • Chrony: Synchronisation horaire gérée par l'hôte" - if detect_lxc && ! is_port_open "22022"; then - echo " • SSH: Port 22 maintenu (port 22022 non disponible en LXC)" - fi - echo "" - } - - echo -e "${YELLOW}🔍 COMMANDES UTILES POST-INSTALLATION:${NC}" - echo " 1. État Fail2ban: sudo fail2ban-client status sshd" - echo " 2. Logs SSH: sudo tail -f /var/log/auth.log" - echo " 3. Ports ouverts: sudo ss -tlnp" - echo " 4. Règles UFW: sudo ufw status verbose" - echo " 5. Score Lynis: grep 'Hardening index' $SECURITY_REPORT" - echo " 6. Suggestions: grep 'Suggestion' $SECURITY_REPORT" - echo " 7. Rapport complet: cat $SECURITY_REPORT | less" + echo -e "${YELLOW}🔍 COMMANDES UTILES:${NC}" + echo " 1. Test SSH: ssh -p $ssh_port $(whoami)@localhost" + echo " 2. Statut UFW: ufw status verbose" + echo " 3. Statut Fail2ban: fail2ban-client status" + echo " 4. Vérification AIDE: aide --check" echo "" - # Interpréter le score - if [[ "$lynis_score" != "N/A" ]]; then - echo -e "${CYAN}💡 INTERPRÉTATION DU SCORE:${NC}" - if [[ $lynis_score -ge 80 ]]; then - echo " ✓ Excellente sécurité ! Votre système est bien durci." - echo " → Continuez à surveiller et à appliquer les mises à jour." - elif [[ $lynis_score -ge 60 ]]; then - echo " ✓ Bonne sécurité, mais des améliorations sont possibles." - echo " → Consultez les suggestions Lynis pour progresser." - elif [[ $lynis_score -ge 40 ]]; then - echo " ⚠ Sécurité moyenne, améliorations recommandées." - echo " → Examinez attentivement les avertissements et suggestions." - else - echo " ⚠ Score faible - plusieurs améliorations nécessaires." - echo " → Priorisez les warnings critiques de Lynis." - fi + if [[ "$ssh_port" != "22" ]] && [[ "$AUTO_CLEANUP_SSH" != "yes" ]]; then + echo -e "${RED}⚠ ACTION REQUISE:${NC}" + echo " Le port SSH 22 est encore actif temporairement." + echo " Après avoir testé le port $ssh_port, exécutez:" + echo " $0 --cleanup-ssh" + echo " Ou redémarrez avec: AUTO_CLEANUP_SSH=yes $0 --unattended" echo "" fi - if [[ "$ssh_port" == "22022" ]]; then - echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" - echo -e "${RED}⚠ TESTEZ SSH:22022 IMMÉDIATEMENT AVANT TOUTE AUTRE ACTION ⚠${NC}" - echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" - fi - - # Nettoyage - rm -f /tmp/lynis_score.txt /tmp/lynis_max_score.txt 2>/dev/null || true + echo -e "${GREEN}✅ Le système est maintenant durci et sécurisé.${NC}" } main() { - # Mode nettoyage SSH - [[ $# -eq 1 && "$1" == "--cleanup-ssh" ]] && { - cleanup_ssh_port - return 0 - } + parse_arguments "$@" + + if $LIST_STEPS; then + list_all_steps + exit 0 + fi + + if $SHOW_STATUS; then + show_step_status + exit 0 + fi + + if [[ "$AUTO_CLEANUP_SSH" == "yes" ]] && [[ $# -eq 0 ]]; then + cleanup_ssh_port + exit 0 + fi - # Initialisation check_requirements print_header log_message "==================================================" "START" - log_message "Démarrage durcissement système v7.0" "START" - log_message "Système: $(hostname)" "START" - log_message "Type: $(detect_container && echo "Conteneur" || echo "Physique/VM")" "START" + log_message "Démarrage durcissement système v8.1" "START" + log_message "Mode: $( [[ "$UNATTENDED" == true ]] && echo "Autonome" || echo "Interactif" )" "START" + log_message "Port SSH: $AUTO_SSH_PORT" "START" + log_message "Hostname: $(hostname)" "START" log_message "==================================================" "START" - # EXÉCUTION DES ÉTAPES + # Exécution des étapes install_security_tools detect_open_ports configure_process_accounting @@ -2047,7 +2115,7 @@ main() { configure_umask configure_aide_sha512 initialize_aide_db - configure_clamav + [[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && configure_clamav configure_chrony harden_ssh configure_banners @@ -2069,17 +2137,22 @@ main() { prepare_ssh_cleanup run_lynis_audit - # Résumé + # Nettoyage SSH si demandé + if [[ "$AUTO_CLEANUP_SSH" == "yes" ]] || auto_confirm; then + cleanup_ssh_port + fi + print_summary - log_message "==================================================" "END" - log_message "Durcissement terminé - Durée: $((SECONDS / 60)) min" "END" - log_message "Ports détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun")" "END" + log_message "Durcissement terminé avec succès" "END" + log_message "Port SSH final: $(get_ssh_port_to_use)" "END" log_message "==================================================" "END" } -# Gestion signaux +# ============================================================================== +# EXÉCUTION PRINCIPALE +# ============================================================================== + trap 'print_error "Script interrompu"; exit 130' INT TERM -# Point d'entrée -[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@" \ No newline at end of file +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"