#!/bin/bash ################################################################################ # Script: system_hardening_optimized.sh # Version: 8.2 # Date: $(date +%Y-%m-%d) # Author: Security Team # Description: Système de durcissement sécurité pour Debian/Ubuntu LTS # Mode autonome avec valeurs par défaut sécurisées # License: GPLv3 ################################################################################ set -euo pipefail # ============================================================================== # 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 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}" : "${AUTO_CHANGE_ROOT_PWD:=yes}" : "${AUTO_SKIP_DEBSUMS_CONTAINER:=yes}" TOTAL_STEPS=33 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 # ============================================================================== readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly CYAN='\033[0;36m' 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é" ["change_root_password"]="Changement mot de passe root aléatoire" ["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 # ============================================================================== log_message() { local message="$1" local level="${2:-INFO}" echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE" } print_step() { local step_title="$1" echo -e "\n${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${YELLOW} ÉTAPE ${CURRENT_STEP}/${TOTAL_STEPS}: $step_title${NC}" echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}" log_message "Début étape $CURRENT_STEP: $step_title" "STEP" CURRENT_STEP=$((CURRENT_STEP + 1)) } print_success() { echo -e "${GREEN}✓${NC} $1" log_message "$1" "SUCCESS" } print_warning() { echo -e "${YELLOW}⚠${NC} $1" log_message "$1" "WARNING" } print_error() { echo -e "${RED}✗${NC} $1" >&2 log_message "$1" "ERROR" } print_info() { echo -e "${BLUE}ℹ${NC} $1" log_message "$1" "INFO" } auto_confirm() { [[ "$AUTO_YES" == "yes" ]] && return 0 [[ "$UNATTENDED" == true ]] && return 0 return 1 } # ============================================================================== # GESTION DES ÉTAPES # ============================================================================== check_step_done() { 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() { 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() { local step_name="$1" print_info "Étape déjà effectuée : $step_name" CURRENT_STEP=$((CURRENT_STEP + 1)) } detect_container() { # ----------------------------------------------------------------- # DÉTECTION DE TOUS TYPES DE CONTENEURS (LXC, Docker, systemd-nspawn, etc.) # Retourne 0 si dans un conteneur, 1 sinon # ----------------------------------------------------------------- # 1. FICHIERS SPÉCIFIQUES [[ -f /.dockerenv ]] && { log_message "Conteneur détecté: /.dockerenv" "DETECT"; return 0; } [[ -d /dev/lxc ]] && { log_message "Conteneur détecté: /dev/lxc" "DETECT"; return 0; } [[ -f /.lxcpid ]] && { log_message "Conteneur détecté: /.lxcpid" "DETECT"; return 0; } # 2. VARIABLES D'ENVIRONNEMENT (utilisation de ${var:-} pour éviter "unbound variable") [[ -n "${container:-}" ]] && { log_message "Conteneur détecté: variable \$container=$container" "DETECT"; return 0; } [[ -n "${DOCKER_HOST:-}" ]] && { log_message "Conteneur détecté: variable \$DOCKER_HOST" "DETECT"; return 0; } [[ -n "${KUBERNETES_SERVICE_HOST:-}" ]] && { log_message "Conteneur détecté: Kubernetes" "DETECT"; return 0; } # 3. CGROUPS (compatible v1 et v2) if [[ -f /proc/1/cgroup ]]; then # Lecture de /proc/1/cgroup (plus fiable que /proc/self/cgroup) local cgroup_content cgroup_content=$(cat /proc/1/cgroup 2>/dev/null || echo "") # Modèles de conteneurs dans cgroups if echo "$cgroup_content" | grep -qE "(docker|lxc|kubepods|containerd|podman|buildkit|systemd-nspawn|libpod)"; then log_message "Conteneur détecté: cgroups pattern" "DETECT" return 0 fi # Cgroups v2: format "0::/" if echo "$cgroup_content" | grep -q "^0::/"; then # Vérifier si le chemin n'est pas un chemin système standard local cgroup_path cgroup_path=$(echo "$cgroup_content" | grep "^0::/" | cut -d: -f3) # Les chemins typiques des conteneurs if [[ "$cgroup_path" == "/" ]] || [[ "$cgroup_path" == /user.slice* ]] || [[ "$cgroup_path" == /system.slice* ]]; then # Pour cgroups v2, vérifier l'isolation des namespaces if [[ -r /proc/1/ns/pid ]] && [[ -r /proc/self/ns/pid ]]; then local pid1_inode local self_inode pid1_inode=$(stat -Lc '%i' /proc/1/ns/pid 2>/dev/null) self_inode=$(stat -Lc '%i' /proc/self/ns/pid 2>/dev/null) # Si les inodes sont différents, on est dans un namespace PID isolé (conteneur) if [[ -n "$pid1_inode" ]] && [[ -n "$self_inode" ]] && [[ "$pid1_inode" != "$self_inode" ]]; then log_message "Conteneur détecté: namespace PID isolé (cgroups v2)" "DETECT" return 0 fi fi else # Chemin non standard -> probablement un conteneur log_message "Conteneur détecté: cgroups v2 path non standard: $cgroup_path" "DETECT" return 0 fi fi fi # 4. SYSTEMD-DETECT-VIRT (outil natif) if command -v systemd-detect-virt >/dev/null 2>&1; then local virt virt=$(systemd-detect-virt 2>/dev/null) case "$virt" in "container"|"lxc"|"lxc-libvirt"|"systemd-nspawn"|"docker"|"podman"|"wsl") log_message "Conteneur détecté: systemd-detect-virt=$virt" "DETECT" return 0 ;; esac fi # 5. VÉRIFICATION DES PROCESSUS ET NAMESPACES # Vérifier l'isolation des namespaces (méthode avancée) if [[ -r /proc/1/ns/pid ]] && [[ -r /proc/self/ns/pid ]]; then local ns_pid1 local ns_self ns_pid1=$(readlink /proc/1/ns/pid 2>/dev/null) ns_self=$(readlink /proc/self/ns/pid 2>/dev/null) if [[ -n "$ns_pid1" ]] && [[ -n "$ns_self" ]] && [[ "$ns_pid1" != "$ns_self" ]]; then log_message "Conteneur détecté: namespace PID différent (PID1: $ns_pid1, Self: $ns_self)" "DETECT" return 0 fi fi # 6. VÉRIFICATION DES PROCESSUS DE GESTION DE CONTENEURS if ps -eo comm 2>/dev/null | grep -qE "^(containerd|dockerd|runc|crun|podman|CRIO)$"; then # Si un gestionnaire de conteneurs tourne, vérifier si nous sommes dans un conteneur if [[ "$(unshare --pid --fork --mount-proc readlink /proc/1/exe 2>/dev/null)" != "/sbin/init" ]]; then log_message "Conteneur détecté: gestionnaire de conteneurs actif" "DETECT" return 0 fi fi # 7. VÉRIFICATION DE L'INIT (PID 1) local init_cmd init_cmd=$(ps -p 1 -o comm= 2>/dev/null || cat /proc/1/comm 2>/dev/null) case "$init_cmd" in "systemd"|"init"|"upstart") # Init système normal - probablement pas un conteneur ;; "containerd-shim"|"runc"|"docker-init"|"tini") log_message "Conteneur détecté: init spécial: $init_cmd" "DETECT" return 0 ;; esac # 8. FICHIERS SPÉCIFIQUES À CERTAINS CONTENEURS [[ -f /run/.containerenv ]] && { log_message "Conteneur détecté: /run/.containerenv" "DETECT"; return 0; } [[ -d /run/host ]] && { # /run/host existe souvent dans les conteneurs systemd-nspawn if ! mountpoint -q /run/host 2>/dev/null; then log_message "Conteneur détecté: /run/host présent" "DETECT" return 0 fi } # 9. VOLUMES DOCKER SPÉCIFIQUES (optionnel) if [[ -d /var/lib/docker ]] && mountpoint -q /var/lib/docker 2>/dev/null; then # /var/lib/docker est monté - pourrait être un daemon Docker log_message "Docker détecté via /var/lib/docker monté" "DETECT" # Note: Ceci détecte le daemon Docker, pas forcément qu'on est dans un conteneur fi # Aucune détection positive log_message "Aucun conteneur détecté" "DETECT" return 1 } detect_lxc() { # Détection spécifique LXC [[ -d /dev/lxc ]] && return 0 [[ -f /.lxcpid ]] && return 0 grep -q "lxc" /proc/1/cgroup 2>/dev/null && return 0 [[ "$(systemd-detect-virt 2>/dev/null)" == "lxc" ]] && return 0 return 1 } backup_file() { local file_path="$1" [[ -f "$file_path" ]] && { mkdir -p "$BACKUP_DIR" cp "$file_path" "${BACKUP_DIR}/" log_message "Sauvegarde de $file_path" "BACKUP" } } add_unique_line() { local line="$1" local file="$2" grep -qF "$line" "$file" 2>/dev/null || { echo "$line" >> "$file" log_message "Ajout ligne: '$line' -> $file" "CONFIG" } } update_config_value() { local file="$1" local key="$2" local value="$3" backup_file "$file" if grep -q "^${key}" "$file"; then sed -i "s/^${key}.*/${key} ${value}/" "$file" else echo "${key} ${value}" >> "$file" fi } # ============================================================================== # 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 } restore_ssh_config() { if [[ -f "${BACKUP_DIR}/sshd_config" ]]; then cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config systemctl restart sshd 2>/dev/null || true print_success "Configuration SSH restaurée" else print_warning "Sauvegarde SSH non trouvée" fi } restore_ufw_config() { if command -v ufw > /dev/null 2>&1; then ufw --force reset > /dev/null 2>&1 || true print_success "Configuration UFW réinitialisée" fi } restore_fail2ban_config() { if [[ -f "${BACKUP_DIR}/jail.local" ]]; 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 } restore_all_configs() { restore_ssh_config restore_ufw_config restore_fail2ban_config } 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 ;; --change-root-pwd) AUTO_CHANGE_ROOT_PWD="yes" print_info "Changement mot de passe root activé" 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.2 ║${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 " --change-root-pwd Changer mot de passe root aléatoirement" 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 " AUTO_CHANGE_ROOT_PWD Changer mdp root aléatoirement (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=yes AUTO_ENABLE_AIDE=yes $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" 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..." # 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) if [[ $port_count -gt 0 ]]; then print_info "Ports détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE")" 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" } is_port_open() { local port="$1" grep -q "^${port}$" "$OPEN_PORTS_FILE" 2>/dev/null } get_ssh_port_to_use() { if detect_lxc; then 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 $AUTO_SSH_PORT non ouvert dans LXC - utilisation du port 22" echo "22" fi else echo "$AUTO_SSH_PORT" fi } # ============================================================================== # FONCTIONS DE DURCISSEMENT (COMPLÈTES) # ============================================================================== configure_banners() { local step_name="configure_banners" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Configuration des bannières légales" # Bannière pour les connexions locales (login) local issue_banner=" ╔══════════════════════════════════════════════════════════════════╗ ║ SYSTÈME SÉCURISÉ ║ ║ ║ ║ Accès strictement réservé aux personnes autorisées. ║ ║ Toute tentative d'accès non autorisé est strictement interdite ║ ║ et fera l'objet de poursuites judiciaires. ║ ║ ║ ║ Toutes les activités sur ce système sont surveillées et ║ ║ enregistrées conformément à la réglementation en vigueur. ║ ║ ║ ║ Date et heure: \$(date) ║ ║ Système: \$(hostname) (\$(uname -o) \$(uname -r)) ║ ╚══════════════════════════════════════════════════════════════════╝ " # Bannière pour SSH (issue.net) local issue_net_banner=" ╔══════════════════════════════════════════════════════════════════╗ ║ SYSTÈME SÉCURISÉ ║ ║ ║ ║ ATTENTION: Accès restreint aux utilisateurs autorisés ║ ║ ║ ║ Vous accédez à un système informatique protégé. ║ ║ Toute utilisation non autorisée est interdite et ║ ║ peut faire l'objet de poursuites. ║ ║ ║ ║ En vous connectant, vous acceptez que votre session ║ ║ soit surveillée et enregistrée. ║ ║ ║ ║ Système: \$(hostname) ║ ║ Adresse IP: \$(who am i | awk '{print \$5}' | sed 's/[()]//g') ║ ╚══════════════════════════════════════════════════════════════════╝ " # Bannière pour motd (Message of the Day) local motd_banner=" ======================================================================== SYSTÈME SÉCURISÉ ------------------------------------------------------------------------ Ce système est configuré pour une haute sécurité. Toutes les connexions sont tracées et enregistrées. Dernière mise à jour sécurité: $(date +%Y-%m-%d) Connexions actives: $(who | wc -l) Uptime: $(uptime -p) ======================================================================== " # Appliquer les bannières print_info "Configuration de /etc/issue (login local)..." echo "$issue_banner" > /etc/issue chmod 644 /etc/issue print_info "Configuration de /etc/issue.net (SSH)..." echo "$issue_net_banner" > /etc/issue.net chmod 644 /etc/issue.net print_info "Configuration de /etc/motd (Message du jour)..." echo "$motd_banner" > /etc/motd chmod 644 /etc/motd # Désactiver les motd dynamiques si existants if [[ -d /etc/update-motd.d ]]; then print_info "Désactivation des motd dynamiques..." chmod -x /etc/update-motd.d/* 2>/dev/null || true fi print_success "Bannières légales configurées" mark_step_done "$step_name" } install_security_tools() { local step_name="install_security_tools" 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é" print_info "Mise à jour des dépôts..." DEBIAN_FRONTEND=noninteractive apt-get update -qq print_info "Mise à jour du système..." DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq # 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" # 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 $base_packages print_success "Système mis à jour et outils installés" mark_step_done "$step_name" } change_root_password() { local step_name="change_root_password" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Changement du mot de passe root" # Ne rien faire en conteneur LXC/Docker if detect_container; then print_warning "Conteneur détecté - changement de mot de passe root ignoré" mark_step_done "$step_name" return 0 fi # Vérifier si on doit changer le mot de passe if [[ "$AUTO_CHANGE_ROOT_PWD" != "yes" ]] && ! auto_confirm; then echo -e "${YELLOW}╔══════════════════════════════════════════════════════════╗${NC}" echo -e "${YELLOW}║ CHANGEMENT MOT DE PASSE ROOT ║${NC}" echo -e "${YELLOW}╚══════════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${RED}⚠ Cette action va changer le mot de passe root de ce système.${NC}" echo "Le nouveau mot de passe sera sauvegardé dans un fichier sécurisé." echo "" echo -e "${YELLOW}Conseils:${NC}" echo " • Notez le mot de passe ou conservez le fichier en lieu sûr" echo " • Utilisez une clé SSH pour l'accès à distance" echo " • Le mot de passe sera perdu si vous ne le sauvegardez pas" echo "" read -p "Voulez-vous changer le mot de passe root ? (o/N): " -r confirm_change if [[ ! "$confirm_change" =~ ^[Oo]$ ]]; then print_info "Changement de mot de passe root annulé" mark_step_done "$step_name" return 0 fi else print_info "Changement automatique du mot de passe root activé" fi # Vérifier si l'utilisateur root a un shell valide if ! grep -q "^root:" /etc/passwd; then print_error "L'utilisateur root n'existe pas" return 1 fi local root_shell=$(grep "^root:" /etc/passwd | cut -d: -f7) if [[ "$root_shell" == "/usr/sbin/nologin" ]] || [[ "$root_shell" == "/bin/false" ]]; then print_warning "Root a un shell non interactif ($root_shell) - changement de mot de passe ignoré" mark_step_done "$step_name" return 0 fi # ============================================================ # MODIFICATION ICI : Utiliser le mot de passe par défaut # ============================================================ print_info "Utilisation du mot de passe par défaut défini..." # Mot de passe par défaut NEW_ROOT_PASSWORD="1H6\$06%@o*iEle" # Fichier pour sauvegarder le mot de passe local PWD_FILE="/root/root_password_$(date +%Y%m%d_%H%M%S).txt" # Sauvegarder le mot de passe (avec permissions restreintes) cat > "$PWD_FILE" << EOF === MOT DE PASSE ROOT - $(hostname) - $(date) === Mot de passe root par défaut: $NEW_ROOT_PASSWORD ⚠ CONSERVEZ CE FICHIER EN LIEU SÛR ⚠ • Ce mot de passe a été défini par défaut dans le script • Il est nécessaire pour les connexions root locales • Utilisez des clés SSH pour les connexions distantes • Vous ne serez PAS obligé de changer ce mot de passe à la première connexion • Le mot de passe expire dans 90 jours (politique système) === FIN === EOF # Restreindre les permissions du fichier chmod 600 "$PWD_FILE" chown root:root "$PWD_FILE" # Changer le mot de passe root SANS forcer le changement print_info "Changement du mot de passe root..." # Utiliser chpasswd avec l'option -c pour utiliser crypt (SHA512) if echo "root:$NEW_ROOT_PASSWORD" | chpasswd -c SHA512 2>/dev/null; then print_success "Mot de passe root changé avec succès" # NE PAS forcer l'expiration immédiate chage -d $(date +%Y-%m-%d) root 2>/dev/null || true # Appliquer la politique d'expiration normale (90 jours) chage -M 90 root 2>/dev/null || true # Afficher les informations de sécurité echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ MOT DE PASSE ROOT MODIFIÉ ║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${YELLOW}Mot de passe défini:${NC} $NEW_ROOT_PASSWORD" echo -e "${YELLOW}Fichier de sauvegarde:${NC} $PWD_FILE" echo -e "${YELLOW}Permissions:${NC} $(stat -c %A "$PWD_FILE" 2>/dev/null || echo "N/A")" echo "" # Afficher le statut du compte root print_info "Statut du compte root:" local last_change=$(chage -l root | grep "Last password change" | cut -d: -f2) local expires=$(chage -l root | grep "Password expires" | cut -d: -f2) echo " • Dernier changement: $last_change" echo " • Expiration: $expires" echo "" # Conseils de sécurité echo "" echo -e "${YELLOW}🔒 CONSEILS DE SÉCURITÉ:${NC}" echo " 1. Sauvegardez le fichier sur un support externe" echo " 2. Utilisez 'sudo' pour les tâches administratives" echo " 3. Configurez l'authentification par clé SSH" echo " 4. Désactivez la connexion root SSH si possible" echo " 5. Le mot de passe expirera dans 90 jours" echo "" # Information pour la session actuelle if [[ "$USER" == "root" ]]; then echo -e "${GREEN}✓ VOTRE SESSION ROOT RESTE ACTIVE${NC}" echo " • Les nouvelles connexions root nécessiteront le nouveau mot de passe" echo " • Vous ne serez PAS forcé de changer le mot de passe à la connexion" echo "" fi else print_error "Échec du changement de mot de passe root" rm -f "$PWD_FILE" 2>/dev/null return 1 fi # Vérifier les accès SSH print_info "Vérification de la configuration SSH..." if grep -q "^PermitRootLogin.*yes" /etc/ssh/sshd_config 2>/dev/null; then echo -e "${YELLOW}⚠ Connexion root SSH activée${NC}" echo " • Considérez la désactivation avec: PermitRootLogin prohibit-password" echo "" fi # Journaliser l'action log_message "Mot de passe root changé (mot de passe par défaut) - Fichier: $PWD_FILE" "SECURITY" mark_step_done "$step_name" } configure_process_accounting() { local step_name="configure_process_accounting" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Configuration du Process Accounting" if detect_container; then print_warning "Conteneur détecté - Process Accounting désactivé" for service in acct.service psacct.service; do systemctl list-unit-files | grep -q "^${service}" && { systemctl stop "$service" 2>/dev/null || true systemctl disable "$service" 2>/dev/null || true systemctl mask "$service" 2>/dev/null || true } done else systemctl enable acct.service 2>/dev/null && \ systemctl start acct.service 2>/dev/null && \ print_success "Process Accounting activé" || \ print_warning "Process Accounting non disponible" fi mark_step_done "$step_name" } 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_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" } 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" } 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_clamav() { local step_name="configure_clamav" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi if [[ "$AUTO_ENABLE_CLAMAV" != "yes" ]]; then print_info "ClamAV désactivé par configuration (AUTO_ENABLE_CLAMAV=no)" mark_step_done "$step_name" return 0 fi print_step "Configuration de ClamAV" # Créer les répertoires nécessaires avec les bonnes permissions print_info "Création des répertoires ClamAV..." mkdir -p /var/log/clamav mkdir -p /var/lib/clamav mkdir -p /run/clamav # Vérifier si l'utilisateur clamav existe, sinon le créer if ! id -u clamav >/dev/null 2>&1; then print_info "Création de l'utilisateur clamav..." useradd -r -M -d /var/lib/clamav -s /bin/false -c "Clam AntiVirus" clamav 2>/dev/null || true fi # Définir les permissions correctes chown -R clamav:clamav /var/log/clamav 2>/dev/null || true chown -R clamav:clamav /var/lib/clamav 2>/dev/null || true chown -R clamav:clamav /run/clamav 2>/dev/null || true chmod 755 /var/log/clamav 2>/dev/null || true chmod 755 /var/lib/clamav 2>/dev/null || true chmod 755 /run/clamav 2>/dev/null || true # Créer les fichiers de log avec les bonnes permissions touch /var/log/clamav/freshclam.log 2>/dev/null || true touch /var/log/clamav/clamav.log 2>/dev/null || true chown clamav:clamav /var/log/clamav/freshclam.log 2>/dev/null || true chown clamav:clamav /var/log/clamav/clamav.log 2>/dev/null || true chmod 640 /var/log/clamav/freshclam.log 2>/dev/null || true chmod 640 /var/log/clamav/clamav.log 2>/dev/null || true # Configuration de freshclam if [[ -f /etc/clamav/freshclam.conf ]]; then backup_file "/etc/clamav/freshclam.conf" # S'assurer que le fichier de log est bien configuré sed -i 's|^#UpdateLogFile.*|UpdateLogFile /var/log/clamav/freshclam.log|' /etc/clamav/freshclam.conf sed -i 's|^UpdateLogFile.*|UpdateLogFile /var/log/clamav/freshclam.log|' /etc/clamav/freshclam.conf # S'assurer que DatabaseOwner est défini if ! grep -q "^DatabaseOwner" /etc/clamav/freshclam.conf; then echo "DatabaseOwner clamav" >> /etc/clamav/freshclam.conf fi fi # Configuration de clamd if [[ -f /etc/clamav/clamd.conf ]]; then backup_file "/etc/clamav/clamd.conf" # S'assurer que le fichier de log est bien configuré sed -i 's|^#LogFile.*|LogFile /var/log/clamav/clamav.log|' /etc/clamav/clamd.conf sed -i 's|^LogFile.*|LogFile /var/log/clamav/clamav.log|' /etc/clamav/clamd.conf fi # Arrêter les services avant la mise à jour print_info "Arrêt temporaire des services ClamAV..." systemctl stop clamav-freshclam 2>/dev/null || true systemctl stop clamav-daemon 2>/dev/null || true # Attendre que les processus se terminent sleep 2 # Mise à jour de la base de données print_info "Mise à jour de la base de données ClamAV..." # Tenter la mise à jour avec freshclam en tant qu'utilisateur clamav if su - clamav -s /bin/bash -c "freshclam --quiet" 2>&1 | tee -a "$LOG_FILE"; then print_success "Base de données ClamAV mise à jour" else # Si ça échoue, essayer en root avec les bonnes permissions print_warning "Tentative de mise à jour en root..." if freshclam --user=clamav --quiet 2>&1 | tee -a "$LOG_FILE"; then print_success "Base de données ClamAV mise à jour" else print_warning "Échec mise à jour ClamAV - les services démarreront avec l'ancienne base" fi fi # Vérifier que les fichiers de base de données existent if [[ ! -f /var/lib/clamav/main.cvd ]] && [[ ! -f /var/lib/clamav/main.cld ]]; then print_warning "Base de données principale ClamAV manquante" print_info "Tentative de téléchargement manuel..." cd /var/lib/clamav wget -q http://database.clamav.net/main.cvd 2>/dev/null || \ print_warning "Impossible de télécharger la base principale" wget -q http://database.clamav.net/daily.cvd 2>/dev/null || \ print_warning "Impossible de télécharger la base quotidienne" wget -q http://database.clamav.net/bytecode.cvd 2>/dev/null || \ print_warning "Impossible de télécharger la base bytecode" chown clamav:clamav /var/lib/clamav/*.cvd 2>/dev/null || true cd - > /dev/null fi # Activer et démarrer les services print_info "Démarrage des services ClamAV..." systemctl enable clamav-freshclam 2>/dev/null || true systemctl enable clamav-daemon 2>/dev/null || true if systemctl start clamav-freshclam 2>&1 | tee -a "$LOG_FILE"; then print_success "Service freshclam démarré" else print_warning "Service freshclam non démarré" fi # Attendre un peu avant de démarrer le daemon sleep 3 if systemctl start clamav-daemon 2>&1 | tee -a "$LOG_FILE"; then print_success "Service clamav-daemon démarré" else print_warning "Service clamav-daemon non démarré" fi # Vérification du statut echo "" print_info "Vérification des services ClamAV..." if systemctl is-active --quiet clamav-freshclam 2>/dev/null; then echo " ✓ freshclam: actif" else echo " ⚠ freshclam: inactif" fi if systemctl is-active --quiet clamav-daemon 2>/dev/null; then echo " ✓ clamav-daemon: actif" else echo " ⚠ clamav-daemon: inactif" fi # Afficher la version de la base de données if command -v sigtool > /dev/null 2>&1; then local db_version=$(sigtool --info /var/lib/clamav/main.cvd 2>/dev/null | grep "Version" | head -1) [[ -n "$db_version" ]] && echo " • Base de données: $db_version" fi print_success "ClamAV configuré" 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" } 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" # DÉTERMINER LE PORT SSH À UTILISER local ssh_port="22" # Par défaut if detect_lxc; then print_info "Conteneur LXC détecté - utilisation du port 22" ssh_port="22" else ssh_port="$AUTO_SSH_PORT" # Valider que le port est un nombre entre 1 et 65535 if ! [[ "$ssh_port" =~ ^[0-9]+$ ]] || [ "$ssh_port" -lt 1 ] || [ "$ssh_port" -gt 65535 ]; then print_warning "Port SSH invalide ($ssh_port) - utilisation du port 22" ssh_port="22" fi fi print_info "Configuration SSH avec le port: $ssh_port" # CRÉER UNE CONFIGURATION SSH SÉCURISÉE # D'abord créer un fichier temporaire local temp_config=$(mktemp) # Construire la configuration ligne par ligne pour éviter les problèmes de formatage { echo "# ============================================================================" echo "# Configuration SSH sécurisée - Générée automatiquement $(date)" echo "# ============================================================================" echo "" # Section Ports echo "# Ports d'écoute" if [[ "$ssh_port" != "22" ]] && ! detect_lxc; then echo "Port 22" echo "Port $ssh_port" echo "# Port 22 maintenu temporairement pour la transition" else echo "Port $ssh_port" fi echo "" echo "# Adresse d'écoute" echo "ListenAddress 0.0.0.0" echo "ListenAddress ::" echo "" echo "# Authentification" if [[ "$AUTO_DISABLE_ROOT_LOGIN" == "yes" ]]; then echo "PermitRootLogin no" print_info "Connexion root SSH désactivée" else echo "PermitRootLogin prohibit-password" print_info "Connexion root SSH autorisée uniquement par clé" fi echo "PasswordAuthentication no" echo "PubkeyAuthentication yes" echo "PermitEmptyPasswords no" echo "MaxAuthTries 3" echo "LoginGraceTime 60" echo "ClientAliveInterval 300" echo "ClientAliveCountMax 2" echo "MaxSessions 2" echo "" echo "# Sécurité" echo "Protocol 2" echo "StrictModes yes" echo "UseDNS no" echo "IgnoreRhosts yes" echo "HostbasedAuthentication no" echo "PrintLastLog yes" echo "" echo "# Redirections" echo "X11Forwarding no" echo "AllowAgentForwarding no" echo "AllowTcpForwarding no" echo "TCPKeepAlive no" echo "" echo "# Performances et logs" echo "Compression no" echo "LogLevel VERBOSE" echo "" echo "# Bannière" echo "Banner /etc/issue.net" echo "" echo "# Algorithmes de chiffrement modernes" echo "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" echo "MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com" echo "KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256" echo "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" echo "" echo "# ============================================================================" } > "$temp_config" # VÉRIFIER LE FICHIER TEMPORAIRE print_info "Vérification du fichier temporaire..." if ! sshd -t -f "$temp_config" 2>&1; then print_error "Configuration temporaire invalide - affichage du contenu :" cat -n "$temp_config" print_info "Tentative avec configuration minimaliste..." # Configuration minimaliste pour LXC cat > "$temp_config" << EOF Port 22 Protocol 2 PermitRootLogin prohibit-password PasswordAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no X11Forwarding no AllowTcpForwarding no AllowAgentForwarding no ChallengeResponseAuthentication no UsePAM yes PrintLastLog yes TCPKeepAlive no ClientAliveInterval 300 ClientAliveCountMax 2 EOF if ! sshd -t -f "$temp_config" 2>&1; then print_error "Configuration minimaliste aussi invalide" rm -f "$temp_config" return 1 fi fi # COPIER LA CONFIGURATION cp "$temp_config" "$sshd_config" chmod 600 "$sshd_config" chown root:root "$sshd_config" rm -f "$temp_config" # AFFICHER LES PREMIÈRES LIGNES POUR VÉRIFICATION print_info "Premières lignes de la configuration :" head -n 15 "$sshd_config" | cat -n # TEST FINAL ET APPLICATION print_info "Test final de la configuration SSH..." if sshd -t -f "$sshd_config" 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)" print_warning "Testez: ssh -p $ssh_port $(whoami)@localhost" print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" else print_info "Port SSH: $ssh_port" 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 après copie" print_info "Contenu complet du fichier :" cat -n "$sshd_config" 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 skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Configuration des règles de pare-feu" 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 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 0 fi # 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 # Port SSH local ssh_port=$(get_ssh_port_to_use) ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé' # Si nouveau port, garder temporairement le port 22 if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then ufw allow 22/tcp comment 'SSH temporaire' fi # Ports essentiels (toujours autorisés) ufw allow 22/tcp comment 'SSH' 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 read -r port; do [[ "$port" == "22" || "$port" == "$ssh_port" || "$port" == "53" || "$port" == "123" ]] && continue # 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_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 ufw deny "${port}/tcp" comment "Port bloqué $port" print_info "Port $port/tcp bloqué" fi 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" fi # Activation echo "y" | ufw --force enable > /dev/null 2>&1 # Vérification if ufw status | grep -q "Status: active"; then print_success "Pare-feu UFW configuré et activé" print_info "Règles appliquées:" ufw status numbered | grep -E "ALLOW|DENY" else print_warning "UFW activé avec avertissements" fi mark_step_done "$step_name" } 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" } remove_unneeded_packages() { local step_name="remove_unneeded_packages" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Suppression des paquets inutiles" local packages_to_remove=(telnet rsh-client rsh-server netcat-openbsd netcat-traditional nis talk talkd) for package in "${packages_to_remove[@]}"; do dpkg -l | grep -q "^ii.*${package}" && { DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "$package" 2>/dev/null print_info "Paquet $package supprimé" } done DEBIAN_FRONTEND=noninteractive apt-get autoremove -y -qq print_success "Paquets inutiles supprimés" mark_step_done "$step_name" } restrict_file_permissions() { local step_name="restrict_file_permissions" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Restriction des permissions des fichiers critiques" chmod 644 /etc/passwd 2>/dev/null || true chmod 600 /etc/shadow 2>/dev/null || true chmod 644 /etc/group 2>/dev/null || true chmod 600 /etc/gshadow 2>/dev/null || true chmod 600 /etc/sudoers 2>/dev/null || true chmod 750 /etc/sudoers.d 2>/dev/null || true chmod 600 /boot/grub/grub.cfg 2>/dev/null || true chmod 644 /etc/ssh/ssh_config 2>/dev/null || true chmod 600 /etc/ssh/sshd_config 2>/dev/null || true print_success "Permissions des fichiers critiques restreintes" mark_step_done "$step_name" } disable_risky_kernel_modules() { local step_name="disable_risky_kernel_modules" 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' blacklist dccp install dccp /bin/true blacklist sctp install sctp /bin/true blacklist rds install rds /bin/true blacklist tipc install tipc /bin/true blacklist cramfs install cramfs /bin/true blacklist freevxfs install freevxfs /bin/true blacklist jffs2 install jffs2 /bin/true blacklist hfs install hfs /bin/true blacklist hfsplus install hfsplus /bin/true blacklist squashfs install squashfs /bin/true blacklist udf install udf /bin/true blacklist firewire-core install firewire-core /bin/true blacklist thunderbolt install thunderbolt /bin/true EOF detect_container && \ print_warning "Conteneur - modules gérés par l'hôte" || \ print_info "Modules blacklistés" print_success "Modules noyau risqués désactivés" mark_step_done "$step_name" } configure_security_limits() { local step_name="configure_security_limits" 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' * hard core 0 * soft nproc 512 * hard nproc 1024 * soft nofile 65536 * hard nofile 65536 EOF print_success "Limites de sécurité configurées" mark_step_done "$step_name" } verify_packages_integrity() { local step_name="verify_packages_integrity" 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..." # Vérifier si debsums est installé if ! command -v debsums > /dev/null 2>&1; then print_warning "debsums n'est pas installé - installation..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq debsums 2>/dev/null || { print_error "Impossible d'installer debsums" mark_step_done "$step_name" return 0 } fi # Vérifier si debsums peut s'exécuter if ! debsums --help > /dev/null 2>&1; then print_warning "debsums ne fonctionne pas correctement" mark_step_done "$step_name" return 0 fi if detect_container; then print_info "Conteneur détecté - filtrage des faux positifs courants..." # Liste des fichiers communément modifiés dans les conteneurs (faux positifs) local ignore_patterns=( "/lib/systemd/system/container-getty@.service" "/etc/hosts" "/etc/resolv.conf" "/etc/hostname" "/etc/mtab" "/etc/localtime" "/etc/timezone" "/etc/machine-id" "/var/lib/dbus/machine-id" ) # Construire l'expression grep pour ignorer ces fichiers local grep_pattern=$(printf "|%s" "${ignore_patterns[@]}") grep_pattern=${grep_pattern:1} # Enlever le premier | # Exécuter debsums en filtrant les faux positifs local debsums_output debsums_output=$(debsums -s 2>&1 || true) if [[ -n "$debsums_output" ]]; then # Filtrer les faux positifs et les erreurs de permission local filtered_output filtered_output=$(echo "$debsums_output" | grep -vE "$grep_pattern" | grep -v "Permission denied" || true) if [[ -n "$filtered_output" ]]; then echo "$filtered_output" | tee -a "$LOG_FILE" # Compter les vraies erreurs local real_errors real_errors=$(echo "$filtered_output" | wc -l) if [[ $real_errors -gt 0 ]]; then print_warning "$real_errors paquet(s) avec des erreurs réelles" # Lister les paquets problématiques echo "" echo "Paquets concernés:" echo "$filtered_output" | awk -F: '{print $1}' | sort -u # Proposer une réparation en mode interactif if ! auto_confirm; then echo "" read -p "Voulez-vous tenter de réparer les paquets corrompus ? (o/N): " -r repair if [[ "$repair" =~ ^[Oo]$ ]]; then print_info "Réparation des paquets..." local packages_to_reinstall packages_to_reinstall=$(echo "$filtered_output" | awk -F: '{print $1}' | sort -u | tr '\n' ' ') if [[ -n "$packages_to_reinstall" ]]; then apt-get install --reinstall -y $packages_to_reinstall 2>&1 | tee -a "$LOG_FILE" || \ print_warning "Certains paquets n'ont pas pu être réinstallés" fi fi fi else print_success "Aucune erreur réelle détectée (faux positifs filtrés)" fi else print_success "Aucune modification détectée (après filtrage)" fi else print_success "Aucune modification détectée" fi else # Système hôte - vérification complète local debsums_output debsums_output=$(debsums -s 2>&1 || true) # Filtrer les erreurs de permission local filtered_output filtered_output=$(echo "$debsums_output" | grep -v "Permission denied" || true) if [[ -n "$filtered_output" ]]; then echo "$filtered_output" | tee -a "$LOG_FILE" print_warning "Certains paquets ont des erreurs" # Lister les paquets problématiques echo "" echo "Paquets concernés:" echo "$filtered_output" | awk -F: '{print $1}' | sort -u # Proposer une réparation if ! auto_confirm; then echo "" read -p "Voulez-vous tenter de réparer ces paquets ? (o/N): " -r repair if [[ "$repair" =~ ^[Oo]$ ]]; then print_info "Réparation des paquets..." local packages_to_reinstall packages_to_reinstall=$(echo "$filtered_output" | awk -F: '{print $1}' | sort -u | tr '\n' ' ') if [[ -n "$packages_to_reinstall" ]]; then apt-get install --reinstall -y $packages_to_reinstall 2>&1 | tee -a "$LOG_FILE" || \ print_warning "Certains paquets n'ont pas pu être réinstallés" fi fi fi else print_success "Tous les paquets sont intacts" fi fi 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" } harden_smtp_banner() { local step_name="harden_smtp_banner" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Durcissement bannière SMTP" if dpkg -l | grep -q "^ii.*postfix"; then backup_file "/etc/postfix/main.cf" postconf -e "smtpd_banner = \$myhostname ESMTP" postconf -e "disable_vrfy_command = yes" systemctl reload postfix 2>/dev/null || true print_success "Bannière Postfix durcie" elif dpkg -l | grep -q "^ii.*exim4"; then backup_file "/etc/exim4/exim4.conf.template" sed -i 's/^smtp_banner.*/smtp_banner = "${primary_hostname} ESMTP"/' \ /etc/exim4/exim4.conf.template 2>/dev/null || true update-exim4.conf 2>/dev/null || true print_success "Bannière Exim4 durcie" else print_info "Aucun serveur SMTP détecté" fi mark_step_done "$step_name" } harden_systemd_services() { local step_name="harden_systemd_services" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Durcissement services systemd" command -v systemd-analyze > /dev/null || { print_warning "systemd-analyze non disponible" mark_step_done "$step_name" return 0 } if detect_container; then print_warning "Conteneur détecté - durcissement systemd limité" local services=(ssh sshd fail2ban chrony) for service in "${services[@]}"; do local unit="${service}.service" local override_dir="/etc/systemd/system/${unit}.d" if [[ -d "$override_dir" ]]; then print_info "Nettoyage des overrides systemd pour $unit" rm -rf "$override_dir" fi done systemctl daemon-reload print_success "Configuration systemd nettoyée" mark_step_done "$step_name" return 0 fi local services=(ssh sshd fail2ban chrony) local hardened=0 for service in "${services[@]}"; do local unit="${service}.service" systemctl list-unit-files | grep -q "^${unit}" || continue if ! systemctl is-enabled "$unit" > /dev/null 2>&1 && \ ! systemctl is-active "$unit" > /dev/null 2>&1; then print_info "Service $unit non actif - ignoré" continue fi mkdir -p "/etc/systemd/system/${unit}.d" if [[ "$service" == "ssh" || "$service" == "sshd" ]]; then cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF [Service] NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=full ProtectHome=read-only ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes EOF else cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF [Service] NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/var/log /var/lib/${service} /run/${service} ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes RestrictSUIDSGID=yes EOF fi hardened=$((hardened + 1)) print_info "Service $unit durci" done systemctl daemon-reload print_info "Vérification des services critiques..." for service in ssh sshd; do local unit="${service}.service" if systemctl list-unit-files | grep -q "^${unit}"; then if systemctl is-active --quiet "$unit" 2>/dev/null; then systemctl restart "$unit" 2>&1 | tee -a "$LOG_FILE" || { print_error "Échec redémarrage $unit - restauration configuration" rm -rf "/etc/systemd/system/${unit}.d" systemctl daemon-reload systemctl restart "$unit" } fi fi done print_success "$hardened service(s) systemd durci(s)" mark_step_done "$step_name" } configure_advanced_pam() { local step_name="configure_advanced_pam" 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" 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 grep -q "rounds=" /etc/pam.d/common-password || \ sed -i 's/pam_unix.so.*/& rounds=500000/' /etc/pam.d/common-password } print_info "Application expiration mots de passe aux utilisateurs..." while IFS=: read -r user _ uid _ _ _ shell; do [[ "$uid" -ge 1000 || "$user" == "root" ]] && \ [[ -n "$shell" ]] && \ [[ "$shell" != *"nologin"* ]] && \ [[ "$shell" != *"false"* ]] && { chage -M 90 "$user" 2>/dev/null || true chage -d 0 "$user" 2>/dev/null || true print_info " → $user: Expiration 90 jours configurée" } done < /etc/passwd print_success "Configuration PAM avancée appliquée" mark_step_done "$step_name" } check_partition_layout() { local step_name="check_partition_layout" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Vérification disposition partitions" local partitions=(/home /tmp /var /var/log /var/log/audit) local warnings=() for partition in "${partitions[@]}"; do mount | grep -q " on ${partition} " && { local device=$(mount | grep " on ${partition} " | awk '{print $1}') print_info " ✓ $partition: Partition séparée ($device)" } || { warnings+=("$partition") print_warning " ⚠ $partition: Non monté séparément" } done [[ ${#warnings[@]} -eq 0 ]] && \ print_success "Toutes les partitions critiques séparées" || { print_warning "${#warnings[@]} partition(s) non séparée(s)" detect_container && \ print_info "Conteneur: partitions gérées par l'hôte" } mark_step_done "$step_name" } check_vmlinuz() { local step_name="check_vmlinuz" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Vérification fichiers noyau" local files=(/vmlinuz /boot/vmlinuz "/boot/vmlinuz-$(uname -r)" "/boot/initrd.img-$(uname -r)") local found=0 for kfile in "${files[@]}"; do [[ -f "$kfile" ]] && { found=$((found + 1)) print_info " ✓ $kfile ($(du -h "$kfile" | cut -f1))" } done [[ $found -eq 0 ]] && { print_warning "Aucun fichier noyau trouvé" detect_container && \ print_info "Conteneur: noyau géré par l'hôte" || { print_error "Système physique: fichiers noyau manquants!" apt-get install --reinstall "linux-image-$(uname -r)" 2>/dev/null || \ print_warning "Échec réinstallation noyau" } } || print_success "$found fichier(s) noyau trouvé(s)" mark_step_done "$step_name" } run_chkrootkit() { local step_name="run_chkrootkit" 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" print_success "Scan chkrootkit terminé" mark_step_done "$step_name" } prepare_ssh_cleanup() { local step_name="prepare_ssh_cleanup" if check_step_done "$step_name"; then skip_step "${STEP_DESCRIPTIONS[$step_name]}" return 0 fi print_step "Préparation nettoyage port SSH 22" local ssh_port=$(get_ssh_port_to_use) if [[ "$ssh_port" == "22022" ]]; then print_warning "Port SSH 22 toujours actif (temporaire)" print_info "Après avoir testé SSH sur le port $ssh_port:" print_info "Exécutez: $0 --cleanup-ssh" else print_info "Conteneur LXC - Port SSH principal: 22" print_info "Aucun nettoyage nécessaire" fi 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 } 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" # Vérifier si unattended-upgrades est installé if ! dpkg -l | grep -q "^ii.*unattended-upgrades"; then print_info "Installation de unattended-upgrades..." DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq unattended-upgrades apt-listchanges needrestart 2>/dev/null || { print_warning "Échec installation complète - continuation avec ce qui est disponible" } fi # Configuration principale de unattended-upgrades backup_file "/etc/apt/apt.conf.d/50unattended-upgrades" cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF' // Configuration des mises à jour automatiques sécurisées Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}"; "${distro_id}:${distro_codename}-security"; "${distro_id}:${distro_codename}-updates"; "${distro_id}ESM:${distro_codename}"; "${distro_id}ESM-Apps:${distro_codename}"; }; // Options de comportement Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::MinimalSteps "true"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Automatic-Reboot-Time "04:00"; Unattended-Upgrade::Automatic-Reboot-WithUsers "false"; // Nettoyage automatique Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; // Notifications par email (si postfix/sendmail est installé) Unattended-Upgrade::Mail "root"; Unattended-Upgrade::MailOnlyOnError "true"; Unattended-Upgrade::MailReport "on-change"; // Téléchargement et installation Unattended-Upgrade::Download-Upgradeable-Packages "true"; Unattended-Upgrade::DevRelease "auto"; // Mise à jour du GRUB si nécessaire Unattended-Upgrade::Update-Boot-App "true"; EOF # Configuration périodique pour APT (ne pas essayer de sauvegarder si le fichier n'existe pas) if [[ -f "/etc/apt/apt.conf.d/10periodic" ]]; then backup_file "/etc/apt/apt.conf.d/10periodic" fi cat > /etc/apt/apt.conf.d/10periodic << 'EOF' // Planification des tâches APT automatiques APT::Periodic::Update-Package-Lists "1"; // Mettre à jour les listes quotidiennement APT::Periodic::Download-Upgradeable-Packages "1"; // Télécharger les paquets upgradables APT::Periodic::AutocleanInterval "7"; // Nettoyer le cache tous les 7 jours APT::Periodic::Unattended-Upgrade "1"; // Exécuter unattended-upgrade APT::Periodic::Verbose "0"; // Mode silencieux APT::Periodic::RandomSleep "300"; // Délai aléatoire pour éviter la charge simultanée EOF # Configuration pour apt-listchanges (uniquement si le fichier existe) if [[ -f "/etc/apt/listchanges.conf" ]]; then backup_file "/etc/apt/listchanges.conf" sed -i 's/^frontend=.*/frontend=mail/' /etc/apt/listchanges.conf 2>/dev/null || true sed -i 's/^which=.*/which=both/' /etc/apt/listchanges.conf 2>/dev/null || true sed -i 's/^email_address=.*/email_address=root/' /etc/apt/listchanges.conf 2>/dev/null || true sed -i 's/^confirm=.*/confirm=0/' /etc/apt/listchanges.conf 2>/dev/null || true else # Créer une configuration minimale si le fichier n'existe pas cat > /etc/apt/listchanges.conf << 'EOF' 2>/dev/null || true [apt] frontend=mail email_address=root confirm=0 save_seen=/var/lib/apt/listchanges.db which=both EOF fi # Configuration pour needrestart (gestion conditionnelle) if [[ -f "/etc/needrestart/needrestart.conf" ]]; then backup_file "/etc/needrestart/needrestart.conf" sed -i 's/^#\$nrconf{restart}.*/\$nrconf{restart} = '"'a'"';/' /etc/needrestart/needrestart.conf 2>/dev/null || true sed -i 's/^#\$nrconf{override_rc}.*/\$nrconf{override_rc} = '"'0'"';/' /etc/needrestart/needrestart.conf 2>/dev/null || true else # Créer le répertoire et le fichier si nécessaire mkdir -p /etc/needrestart/ 2>/dev/null || true cat > /etc/needrestart/needrestart.conf << 'EOF' 2>/dev/null || true # Configuration de needrestart $nrconf{restart} = 'a'; # Redémarrer automatiquement les services $nrconf{override_rc} = 0; # Ne pas modifier les scripts rc $nrconf{html_browser} = ''; # Pas de navigateur HTML EOF fi # Configuration du timer systemd (optionnel) if command -v systemctl >/dev/null 2>&1; then # Créer le répertoire pour les overrides mkdir -p /etc/systemd/system/apt-daily.timer.d/ 2>/dev/null || true # Ne pas essayer de sauvegarder un fichier qui n'existe pas if [[ -f "/etc/systemd/system/apt-daily.timer" ]]; then backup_file "/etc/systemd/system/apt-daily.timer" fi # Créer l'override cat > /etc/systemd/system/apt-daily.timer.d/override.conf << 'EOF' 2>/dev/null || true [Timer] OnCalendar= OnCalendar=*-*-* 3:00 RandomizedDelaySec=1h EOF systemctl daemon-reload 2>/dev/null || true fi # Activer le service print_info "Activation du service unattended-upgrades..." if systemctl is-enabled unattended-upgrades >/dev/null 2>&1; then systemctl restart unattended-upgrades 2>&1 | tee -a "$LOG_FILE" || true print_info "Service unattended-upgrades redémarré" else systemctl enable unattended-upgrades 2>&1 | tee -a "$LOG_FILE" || true systemctl start unattended-upgrades 2>&1 | tee -a "$LOG_FILE" || true print_info "Service unattended-upgrades activé et démarré" fi # Vérification simple sleep 2 if systemctl is-active unattended-upgrades >/dev/null 2>&1; then print_success "Service unattended-upgrades actif" else print_warning "Service unattended-upgrades inactif (démarrage différé)" fi # Tester la configuration (optionnel) if command -v unattended-upgrade >/dev/null 2>&1; then print_info "Test de configuration unattended-upgrades..." unattended-upgrade --dry-run --debug 2>&1 | grep -i "checking\|result" | head -5 | tee -a "$LOG_FILE" || true fi # Afficher un résumé echo "" print_info "Configuration des mises à jour automatiques:" echo " ✓ Configuration unattended-upgrades appliquée" echo " ✓ Planification quotidienne configurée" echo " ✓ Mises à jour de sécurité activées" echo " ✓ Nettoyage automatique activé" echo "" print_success "Mises à jour automatiques configurées" mark_step_done "$step_name" } # ============================================================================== # 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.2 ║" 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 } print_summary() { echo -e "\n${GREEN}" echo "╔══════════════════════════════════════════════════════════════════╗" echo "║ DURCISSEMENT SYSTÈME TERMINÉ AVEC SUCCÈS ║" echo "╚══════════════════════════════════════════════════════════════════╝" echo -e "${NC}" local ssh_port=$(get_ssh_port_to_use) echo -e "\n${MAGENTA}════════════════════ RÉSUMÉ FINAL ════════════════════${NC}\n" echo -e "${YELLOW}🔐 CONFIGURATIONS APPLIQUÉES:${NC}" echo " • Mot de passe root: $( [[ "$AUTO_CHANGE_ROOT_PWD" == "yes" ]] && echo "Changé aléatoirement" || echo "Non modifié" )" echo " • SSH: Port $ssh_port, authentification par clé uniquement" echo " • Pare-feu: $( [[ "$AUTO_ENABLE_UFW" == "yes" ]] && echo "UFW activé" || echo "UFW désactivé" )" echo " • Fail2ban: $( [[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && echo "Activé" || echo "Désactivé" )" echo " • AIDE: $( [[ "$AUTO_ENABLE_AIDE" == "yes" ]] && echo "Base initialisée + vérif. quotidienne" || echo "Désactivé" )" echo " • Mises à jour automatiques: Configurées" echo " • Politique mot de passe: 14 caractères minimum, 3 classes" 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 "" 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 "" 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 echo -e "${GREEN}✅ Le système est maintenant durci et sécurisé.${NC}" } main() { 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 check_requirements print_header log_message "==================================================" "START" log_message "Démarrage durcissement système v8.2" "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 "Changement mdp root: $AUTO_CHANGE_ROOT_PWD" "START" log_message "==================================================" "START" # Exécution des étapes install_security_tools change_root_password detect_open_ports configure_process_accounting configure_sysctl_security configure_log_permissions configure_pam_password_policy configure_login_defs configure_umask configure_aide_sha512 initialize_aide_db [[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && configure_clamav configure_chrony harden_ssh configure_banners configure_firewall_ports configure_fail2ban remove_unneeded_packages restrict_file_permissions disable_risky_kernel_modules configure_security_limits verify_packages_integrity configure_automatic_updates configure_aide_cron harden_smtp_banner harden_systemd_services configure_advanced_pam check_partition_layout check_vmlinuz run_chkrootkit prepare_ssh_cleanup run_lynis_audit # Nettoyage SSH si demandé if [[ "$AUTO_CLEANUP_SSH" == "yes" ]] || auto_confirm; then cleanup_ssh_port fi print_summary log_message "Durcissement terminé avec succès" "END" log_message "Port SSH final: $(get_ssh_port_to_use)" "END" log_message "==================================================" "END" } # ============================================================================== # EXÉCUTION PRINCIPALE # ============================================================================== trap 'print_error "Script interrompu"; exit 130' INT TERM [[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"