#!/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}" 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() { grep -qE "lxc|container" /proc/self/cgroup 2>/dev/null || \ [[ -f /.dockerenv ]] || [[ -d /dev/lxc ]] } detect_lxc() { grep -q "lxc" /proc/self/cgroup 2>/dev/null || [[ -d /dev/lxc ]] } 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) # ============================================================================== 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" 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_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" local banner_text="╔════════════════════════════════════════════════════════════╗ ║ SYSTÈME SÉCURISÉ ║ ║ ║ ║ Accès réservé aux personnes autorisées uniquement. ║ ║ Toute tentative d'accès non autorisée est interdite ║ ║ et sera tracée et poursuivie selon la loi. ║ ║ ║ ║ Les activités sont surveillées et enregistrées. ║ ╚════════════════════════════════════════════════════════════╝" echo "$banner_text" | tee /etc/issue /etc/issue.net /etc/motd > /dev/null print_success "Bannières légales configurées" 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..." 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" } 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" } 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 } # ============================================================================== # 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 "$@"