#!/bin/bash ################################################################################ # Script: system_hardening_optimized.sh # Version: 7.0 # Date: $(date +%Y-%m-%d) # Author: Security Team # Description: Système de durcissement sécurité pour Debian/Ubuntu LTS # avec détection automatique des ports ouverts # License: GPLv3 ################################################################################ set -euo pipefail # ============================================================================== # CONFIGURATION # ============================================================================== readonly LOG_FILE="/var/log/system_hardening.log" readonly STATUS_FILE="/var/log/hardening_status.log" readonly BACKUP_DIR="/root/backup_hardening_$(date +%Y%m%d_%H%M%S)" readonly SECURITY_REPORT="/var/log/security_report_$(date +%Y%m%d).log" readonly NEW_SSH_PORT=22022 readonly OPEN_PORTS_FILE="/tmp/open_ports_detected.txt" TOTAL_STEPS=31 CURRENT_STEP=1 # ============================================================================== # 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' # ============================================================================== # 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" } check_step_done() { grep -q "^${1}$" "$STATUS_FILE" 2>/dev/null } mark_step_done() { echo "$1" >> "$STATUS_FILE" log_message "Étape '$1' marquée comme terminée" "STATUS" } skip_step() { print_info "Étape déjà effectuée : $1" 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 DÉTECTION DES PORTS # ============================================================================== detect_open_ports() { local step_name="detect_open_ports" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Détection des ports ouverts" print_info "Scan des ports en écoute localement..." # Utiliser ss pour détecter les ports en écoute ss -tlnp | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" 2>/dev/null || { # Fallback sur netstat si ss n'est pas disponible netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" || { print_warning "Impossible de détecter les ports ouverts" touch "$OPEN_PORTS_FILE" } } 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é" 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 22022..." if is_port_open "22022"; then print_info "Port 22022 déjà ouvert dans LXC" echo "22022" else print_warning "Port 22022 non ouvert dans LXC - utilisation du port 22" echo "22" fi else # Pour les systèmes physiques/VMs, utiliser le nouveau port echo "22022" fi } # ============================================================================== # FONCTIONS DE DURCISSEMENT (adaptées) # ============================================================================== # ÉTAPE 1: Mise à jour système et installation outils install_security_tools() { local step_name="install_security_tools" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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 local packages="lynis aide aide-common fail2ban ufw libpam-pwquality apt-listchanges \ apt-listbugs needrestart clamav clamav-daemon chrony chkrootkit \ libpam-tmpdir debsums unattended-upgrades" detect_container || packages+=" acct" print_info "Installation des paquets de sécurité..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $packages print_success "Système mis à jour et outils installés" mark_step_done "$step_name" } # ÉTAPE 2: Détection des ports ouverts (déjà définie plus haut) configure_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 basées sur les ports détectés" # Vérifier si on est dans un conteneur if detect_container; then print_warning "Conteneur détecté - pare-feu géré par l'hôte Proxmox" print_info "UFW ne peut pas être configuré dans un conteneur LXC" # Désactiver UFW s'il est actif if systemctl is-active --quiet ufw 2>/dev/null; then ufw --force disable 2>/dev/null || true systemctl stop ufw 2>/dev/null || true systemctl disable ufw 2>/dev/null || true systemctl mask ufw 2>/dev/null || true print_info "Service UFW désactivé (non nécessaire en conteneur)" fi print_success "Configuration pare-feu ignorée (environnement conteneurisé)" mark_step_done "$step_name" return 0 fi # Configuration pour système physique/VM backup_file "/etc/ufw/user.rules" backup_file "/etc/ufw/user6.rules" print_info "Réinitialisation des règles UFW..." ufw --force reset > /dev/null 2>&1 || { print_error "Impossible de réinitialiser UFW" mark_step_done "$step_name" return 1 } print_info "Configuration des politiques par défaut..." ufw default deny incoming ufw default allow outgoing # Déterminer le port SSH à utiliser local ssh_port=$(get_ssh_port_to_use) # Autoriser le port SSH print_info "Autorisation du port SSH $ssh_port..." if ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé' > /dev/null 2>&1; then print_info " ✓ Port SSH $ssh_port autorisé" else print_warning " ✗ Erreur avec le port SSH $ssh_port" fi # Si on utilise un port différent de 22 et que le port 22 est ouvert, l'autoriser temporairement if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then print_info "Autorisation temporaire du port SSH 22 (à désactiver après test)..." if ufw allow "22/tcp" comment 'SSH temporaire (à désactiver)' > /dev/null 2>&1; then print_info " ✓ Port SSH 22 autorisé temporairement" else print_warning " ✗ Erreur avec le port SSH 22" fi fi # Définition des services courants avec leurs ports (version simplifiée) declare -A service_ports=( # Services web essentiels ["HTTP"]="80" ["HTTPS"]="443" # Services réseau de base ["DNS"]="53" ["DNS-UDP"]="53/udp" ["NTP"]="123" ["NTP-UDP"]="123/udp" # Email ["SMTP"]="25" ["SMTP-Submission"]="587" ["SMTPS"]="465" ["IMAP"]="143" ["IMAPS"]="993" ["POP3"]="110" ["POP3S"]="995" # Base de données ["MySQL"]="3306" ["PostgreSQL"]="5432" ["MongoDB"]="27017" ["Redis"]="6379" ["Elasticsearch"]="9200" # Monitoring & Logging ["Graylog-Web"]="9000" ["Graylog-API"]="12900" ["Prometheus"]="9090" ["Grafana"]="3000" ["Node-Exporter"]="9100" # Services spécifiques demandés ["Ollama"]="11434" ["Gitea"]="3000" ["Gitea-SSH"]="2222" ["Bitwarden"]="80 443" ["Teleport"]="3022 3023 3024 3025 3026" ["NetBox"]="8000" ["Wazuh"]="1514 1515 55000" # Services AD/Samba ["Active-Directory"]="53 88 135 137 138 139 389 445 464 636 3268 3269" ["Samba"]="137 138 139 445" ["LDAP"]="389" ["LDAPS"]="636" ["Kerberos"]="88" # DevOps ["Docker"]="2375 2376" ["Kubernetes"]="6443 10250" ["Jenkins"]="8080" ["GitLab"]="80 443 22" # Virtualisation ["Proxmox"]="8006" ["VNC"]="5900" ["RDP"]="3389" ) print_info "Analyse des ports ouverts pour services connus..." local services_authorized=0 local total_services=${#service_ports[@]} local current_service=0 # Parcourir tous les services for service_name in "${!service_ports[@]}"; do current_service=$((current_service + 1)) local ports="${service_ports[$service_name]}" local ports_array=($ports) # Vérifier chaque port du service for port_entry in "${ports_array[@]}"; do # Extraire le numéro de port (sans /udp ou /tcp) local port_num=$(echo "$port_entry" | grep -o '^[0-9]*') # Vérifier si c'est une plage de ports if [[ "$port_num" =~ ^[0-9]+-[0-9]+$ ]]; then local start_port=$(echo "$port_num" | cut -d'-' -f1) local end_port=$(echo "$port_num" | cut -d'-' -f2) # Vérifier si au moins un port de la plage est ouvert local range_detected=false for ((p=start_port; p<=end_port; p++)); do if is_port_open "$p"; then range_detected=true break fi done if $range_detected; then echo -e "${YELLOW}[$current_service/$total_services] Plage de ports détectée: $port_entry ($service_name)${NC}" read -p " Autoriser la plage $start_port:$end_port ? (o/N): " -r confirm_range if [[ "$confirm_range" =~ ^[OoYy]$ ]]; then # Déterminer le protocole if [[ "$port_entry" == *"/udp" ]]; then if ufw allow "$start_port:$end_port/udp" comment "$service_name" > /dev/null 2>&1; then services_authorized=$((services_authorized + 1)) print_info " ✓ Plage $start_port:$end_port/udp autorisée" else print_warning " ✗ Erreur avec la plage $port_entry" fi else if ufw allow "$start_port:$end_port/tcp" comment "$service_name" > /dev/null 2>&1; then services_authorized=$((services_authorized + 1)) print_info " ✓ Plage $start_port:$end_port/tcp autorisée" else print_warning " ✗ Erreur avec la plage $port_entry" fi fi else print_info " ✗ Plage $port_entry non autorisée" fi fi else # Port unique if is_port_open "$port_num"; then echo -e "${YELLOW}[$current_service/$total_services] Port $port_entry détecté ($service_name)${NC}" # Demander confirmation read -p " Autoriser ce port ? (o/N): " -r confirm_port if [[ "$confirm_port" =~ ^[OoYy]$ ]]; then # Déterminer le protocole if [[ "$port_entry" == *"/udp" ]]; then if ufw allow "$port_num/udp" comment "$service_name" > /dev/null 2>&1; then services_authorized=$((services_authorized + 1)) print_info " ✓ Port $port_num/udp autorisé" else print_warning " ✗ Erreur avec le port $port_num/udp" fi else # Par défaut, autoriser TCP if ufw allow "$port_num/tcp" comment "$service_name" > /dev/null 2>&1; then services_authorized=$((services_authorized + 1)) print_info " ✓ Port $port_num/tcp autorisé" else print_warning " ✗ Erreur avec le port $port_num/tcp" fi # Pour les ports non spécifiés, demander pour UDP aussi if [[ ! "$port_entry" == *"/"* ]]; then echo -n " Autoriser aussi en UDP ? (o/N): " read -r confirm_udp if [[ "$confirm_udp" =~ ^[OoYy]$ ]]; then if ufw allow "$port_num/udp" comment "$service_name (UDP)" > /dev/null 2>&1; then services_authorized=$((services_authorized + 1)) print_info " ✓ Port $port_num/udp autorisé" else print_warning " ✗ Erreur avec le port $port_num/udp" fi fi fi fi else print_info " ✗ Port $port_entry non autorisé" fi fi fi done done # Vérifier les ports restants non couverts print_info "Recherche de ports ouverts supplémentaires..." local unknown_ports_added=0 if [[ -f "$OPEN_PORTS_FILE" ]]; then while IFS= read -r port; do # Nettoyer le port port=$(echo "$port" | tr -d '[:space:]') [[ -z "$port" ]] && continue # Ignorer les ports déjà traités [[ "$port" == "22" || "$port" == "22022" || "$port" == "$ssh_port" ]] && continue # Vérifier si le port est déjà couvert par un service local port_covered=false for service_name in "${!service_ports[@]}"; do local ports="${service_ports[$service_name]}" for port_spec in $ports; do local check_port=$(echo "$port_spec" | grep -o '^[0-9]*') if [[ "$check_port" == "$port" ]]; then port_covered=true break 2 fi done done if ! $port_covered; then # Chercher une description dans /etc/services local service_desc=$(grep -E "^[^#][^[:space:]]+[[:space:]]+$port/" /etc/services 2>/dev/null | awk '{print $1}' | head -1) if [[ -n "$service_desc" ]]; then echo -e "${YELLOW} Port $port détecté ($service_desc)${NC}" else echo -e "${YELLOW} Port $port détecté (service inconnu)${NC}" fi read -p " Autoriser ce port ? (o/N): " -r confirm_port if [[ "$confirm_port" =~ ^[OoYy]$ ]]; then # Autoriser TCP if ufw allow "${port}/tcp" comment "Port $port ${service_desc:+- $service_desc}" > /dev/null 2>&1; then unknown_ports_added=$((unknown_ports_added + 1)) print_info " ✓ Port $port/tcp autorisé" else print_warning " ✗ Erreur avec le port $port/tcp" fi # Demander pour UDP echo -n " Autoriser aussi en UDP ? (o/N): " read -r confirm_udp if [[ "$confirm_udp" =~ ^[OoYy]$ ]]; then if ufw allow "${port}/udp" comment "Port $port/udp ${service_desc:+- $service_desc}" > /dev/null 2>&1; then unknown_ports_added=$((unknown_ports_added + 1)) print_info " ✓ Port $port/udp autorisé" else print_warning " ✗ Erreur avec le port $port/udp" fi fi else print_info " ✗ Port $port non autorisé" fi fi done < "$OPEN_PORTS_FILE" else print_warning "Fichier des ports détectés non trouvé" fi # Configuration ICMP (limitation des pings) print_info "Configuration de la limitation ICMP..." if [[ -f /etc/ufw/before.rules ]]; then backup_file "/etc/ufw/before.rules" # Ajouter la limitation ICMP si elle n'existe pas déjà if ! grep -q "Limiter pings" /etc/ufw/before.rules; then # Chercher la ligne avant la fin du fichier local insert_line=$(grep -n "^COMMIT" /etc/ufw/before.rules | head -1 | cut -d: -f1) if [[ -n "$insert_line" ]]; then # Insérer avant COMMIT sed -i "${insert_line}i# Limiter pings\n-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 3/second --limit-burst 5 -j ACCEPT\n-A ufw-before-input -p icmp --icmp-type echo-request -j DROP" /etc/ufw/before.rules print_info " ✓ Limitation ICMP configurée" else # Ajouter à la fin echo -e "\n# Limiter pings" >> /etc/ufw/before.rules echo "-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 3/second --limit-burst 5 -j ACCEPT" >> /etc/ufw/before.rules echo "-A ufw-before-input -p icmp --icmp-type echo-request -j DROP" >> /etc/ufw/before.rules print_info " ✓ Limitation ICMP ajoutée" fi else print_info " ⏭ Limitation ICMP déjà configurée" fi fi # Activer UFW print_info "Activation du pare-feu UFW..." # Désactiver d'abord pour éviter les conflits ufw --force disable > /dev/null 2>&1 || true # Activer avec confirmation automatique if ufw --force enable > /dev/null 2>&1; then print_success "Pare-feu UFW configuré et activé" # Afficher les statistiques echo "" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN} RÉSUMÉ DE LA CONFIGURATION DU PAREFEU ${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "${BLUE}Services autorisés:${NC} $services_authorized" echo -e "${BLUE}Ports personnalisés:${NC} $unknown_ports_added" echo -e "${BLUE}Port SSH principal:${NC} $ssh_port" if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then echo -e "${YELLOW}Port SSH temporaire:${NC} 22 (à désactiver après test)" fi echo "" # Afficher le statut UFW print_info "Statut UFW actuel:" echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}" ufw status verbose | head -20 echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}" else # Fallback avec confirmation interactive print_warning "Échec de l'activation automatique, tentative manuelle..." if ufw enable <<< 'y' > /dev/null 2>&1; then print_success "Pare-feu UFW activé (mode manuel)" else print_error "Échec critique de l'activation UFW" print_info "Le pare-feu reste désactivé - vérifiez la configuration" fi fi # Sauvegarder la configuration cat > "$BACKUP_DIR/firewall_config_$(date +%Y%m%d_%H%M%S).log" << EOF === CONFIGURATION DU PAREFEU === Date: $(date) Hostname: $(hostname) Port SSH utilisé: $ssh_port Ports ouverts détectés: $(cat "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun port détecté") Services autorisés: $services_authorized Ports personnalisés: $unknown_ports_added Configuration UFW actuelle: $(ufw status) === FIN DE CONFIGURATION === EOF print_info "Configuration sauvegardée dans: $BACKUP_DIR/firewall_config_*.log" # Instructions importantes if [[ "$ssh_port" == "22022" ]]; then echo "" echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" echo -e "${RED} ⚠ IMPORTANT ⚠ ${NC}" echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" echo "" echo -e "${YELLOW}1. Testez immédiatement le nouveau port SSH:${NC}" echo " ssh -p 22022 $(whoami)@$(hostname -I | awk '{print $1}')" echo "" echo -e "${YELLOW}2. Une fois confirmé, supprimez le port 22 avec:${NC}" echo " $0 --cleanup-ssh" echo "" echo -e "${RED}NE FERMEZ PAS CETTE SESSION AVANT DE VALIDER SSH SUR LE PORT 22022${NC}" echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" fi mark_step_done "$step_name" } # ÉTAPE 3: Configuration Process Accounting configure_process_accounting() { local step_name="configure_process_accounting" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 4: Durcissement sysctl configure_sysctl_security() { local step_name="configure_sysctl_security" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Durcissement des paramètres noyau (sysctl)" cat > /etc/sysctl.d/99-security-hardening.conf << 'EOF' # Sécurité réseau IPv4 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.accept_source_route = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.default.log_martians = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 net.ipv4.tcp_syncookies = 1 # Sécurité réseau IPv6 net.ipv6.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv6.conf.default.accept_redirects = 0 net.ipv6.conf.default.accept_source_route = 0 # Sécurité noyau kernel.randomize_va_space = 2 kernel.kptr_restrict = 2 kernel.dmesg_restrict = 1 kernel.yama.ptrace_scope = 1 kernel.unprivileged_bpf_disabled = 1 # Sécurité système fs.suid_dumpable = 0 fs.protected_fifos = 2 fs.protected_regular = 2 fs.protected_symlinks = 1 fs.protected_hardlinks = 1 EOF sysctl -p /etc/sysctl.d/99-security-hardening.conf 2>/dev/null || \ print_warning "Certains paramètres sysctl ignorés" print_success "Paramètres sysctl appliqués" mark_step_done "$step_name" } # ÉTAPE 5: Configuration permissions logs configure_log_permissions() { local step_name="configure_log_permissions" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration des permissions des fichiers de log" find /var/log -type f -exec chmod 640 {} \; 2>/dev/null || true [[ -f /var/log/auth.log ]] && chmod 600 /var/log/auth.log [[ -f /var/log/syslog ]] && chmod 600 /var/log/syslog chown root:adm /var/log/ 2>/dev/null || true chmod 750 /var/log/ 2>/dev/null || true print_success "Permissions des logs configurées" mark_step_done "$step_name" } # ÉTAPE 6: Politique mots de passe PAM configure_pam_password_policy() { local step_name="configure_pam_password_policy" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration de la politique de mots de passe PAM" backup_file "/etc/security/pwquality.conf" cat >> /etc/security/pwquality.conf << 'EOF' # Configuration renforcée minlen = 14 minclass = 3 maxrepeat = 3 maxsequence = 3 dcredit = -1 ucredit = -1 lcredit = -1 ocredit = -1 difok = 3 EOF backup_file "/etc/pam.d/common-password" # Ajouter remember et sha512 sed -i 's/pam_unix.so.*/& remember=5 sha512 rounds=500000/' /etc/pam.d/common-password # Ajouter pwquality si absent grep -q "pam_pwquality.so" /etc/pam.d/common-password || \ sed -i '1i password requisite pam_pwquality.so retry=3' /etc/pam.d/common-password print_success "Politique PAM configurée" mark_step_done "$step_name" } # ÉTAPE 7: Configuration login.defs configure_login_defs() { local step_name="configure_login_defs" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration des paramètres de connexion (login.defs)" backup_file "/etc/login.defs" update_config_value "/etc/login.defs" "PASS_MAX_DAYS" "90" update_config_value "/etc/login.defs" "PASS_MIN_DAYS" "7" update_config_value "/etc/login.defs" "PASS_WARN_AGE" "7" update_config_value "/etc/login.defs" "LOGIN_RETRIES" "3" update_config_value "/etc/login.defs" "LOGIN_TIMEOUT" "60" update_config_value "/etc/login.defs" "UMASK" "027" update_config_value "/etc/login.defs" "ENCRYPT_METHOD" "SHA512" update_config_value "/etc/login.defs" "SHA_CRYPT_MIN_ROUNDS" "500000" update_config_value "/etc/login.defs" "SHA_CRYPT_MAX_ROUNDS" "1000000" print_success "Configuration login.defs appliquée" mark_step_done "$step_name" } # ÉTAPE 8: Configuration umask global configure_umask() { local step_name="configure_umask" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration de l'umask par défaut" for file in /etc/profile /etc/bash.bashrc; do backup_file "$file" sed -i '/^umask/d' "$file" add_unique_line "umask 027" "$file" done print_success "Umask configuré à 027" mark_step_done "$step_name" } # ÉTAPE 9: Configuration AIDE SHA512 configure_aide_sha512() { local step_name="configure_aide_sha512" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration AIDE pour SHA512" backup_file "/etc/aide/aide.conf" grep -q "ALLXTRAHASHES" /etc/aide/aide.conf && \ sed -i 's/ALLXTRAHASHES.*=.*/ALLXTRAHASHES = sha512/' /etc/aide/aide.conf || \ echo "ALLXTRAHASHES = sha512" >> /etc/aide/aide.conf print_success "AIDE configuré pour SHA512" mark_step_done "$step_name" } # ÉTAPE 10: Initialisation AIDE initialize_aide_db() { local step_name="initialize_aide_db" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Initialisation de la base de données AIDE" print_info "Création de la base de données de référence (peut prendre plusieurs minutes)..." if command -v aideinit > /dev/null; then aideinit && print_success "Base de données AIDE initialisée via aideinit" || { print_error "Échec aideinit" return 1 } else aide --init --config /etc/aide/aide.conf && { [[ -f /var/lib/aide/aide.db.new ]] && { mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db print_success "Base de données AIDE initialisée" } || { print_error "Fichier de base de données non trouvé" return 1 } } || { print_error "Échec initialisation AIDE" return 1 } fi mark_step_done "$step_name" } # ÉTAPE 11: Configuration ClamAV configure_clamav() { local step_name="configure_clamav" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration de ClamAV" print_info "Mise à jour de la base de données ClamAV..." systemctl stop clamav-freshclam 2>/dev/null || true freshclam || print_warning "Échec mise à jour ClamAV (normal si déjà récent)" systemctl enable clamav-freshclam systemctl start clamav-freshclam systemctl enable clamav-daemon 2>/dev/null || true systemctl start clamav-daemon 2>/dev/null || true print_success "ClamAV configuré" mark_step_done "$step_name" } # ÉTAPE 12: Configuration Chrony configure_chrony() { local step_name="configure_chrony" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration de la synchronisation horaire (Chrony)" # Vérifier si on est dans un conteneur if detect_container; then print_warning "Conteneur détecté - synchronisation horaire gérée par l'hôte" print_info "La configuration de Chrony est ignorée en environnement conteneurisé" # Désactiver/masquer chrony s'il est présent if systemctl list-unit-files | grep -q "^chrony.service"; then systemctl stop chrony 2>/dev/null || true systemctl disable chrony 2>/dev/null || true systemctl mask chrony 2>/dev/null || true print_info "Service Chrony désactivé (non nécessaire en conteneur)" fi mark_step_done "$step_name" return 0 fi # Configuration pour système physique/VM backup_file "/etc/chrony/chrony.conf" # Configurer le fuseau horaire timedatectl set-timezone Europe/Paris || { print_warning "Impossible de définir le fuseau horaire" } cat > /etc/chrony/chrony.conf << 'EOF' # Serveurs NTP pool 2.debian.pool.ntp.org iburst server 0.fr.pool.ntp.org iburst server 1.fr.pool.ntp.org iburst # Fichiers de drift et log driftfile /var/lib/chrony/chrony.drift logdir /var/log/chrony # Restrictions makestep 1.0 3 rtcsync EOF # Activer et démarrer le service if systemctl enable chrony 2>/dev/null && systemctl restart chrony 2>/dev/null; then print_success "Chrony configuré et démarré" else print_warning "Erreur lors du démarrage de Chrony - service peut-être non disponible" fi mark_step_done "$step_name" } # ÉTAPE 13: Durcissement SSH (adapté pour LXC) harden_ssh() { local step_name="harden_ssh" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Durcissement du service SSH" backup_file "/etc/ssh/sshd_config" local sshd_config="/etc/ssh/sshd_config" # Fonction helper pour mettre à jour une valeur update_ssh_param() { local param="$1" local value="$2" local file="$3" # Supprimer toutes les occurrences (commentées ou non) sed -i "/^#*${param}/d" "$file" # Ajouter la nouvelle valeur echo "${param} ${value}" >> "$file" } # Déterminer le port SSH à utiliser local ssh_port=$(get_ssh_port_to_use) # Configuration des ports sed -i '/^Port /d' "$sshd_config" sed -i '/^#Port /d' "$sshd_config" echo "Port $ssh_port" >> "$sshd_config" # Si LXC et port 22022 non ouvert, garder aussi le port 22 if detect_lxc && [[ "$ssh_port" == "22" ]]; then print_info "LXC détecté - port 22 maintenu (port 22022 non disponible)" elif [[ "$ssh_port" == "22022" ]]; then # Pour les systèmes non-LXC ou LXC avec port 22022 ouvert, ajouter le port 22 temporairement echo "Port 22" >> "$sshd_config" print_info "Port 22 ajouté temporairement (à désactiver après test)" fi # Paramètres de sécurité avec la fonction helper update_ssh_param "PermitRootLogin" "yes" "$sshd_config" update_ssh_param "PasswordAuthentication" "no" "$sshd_config" update_ssh_param "PubkeyAuthentication" "yes" "$sshd_config" update_ssh_param "PermitEmptyPasswords" "no" "$sshd_config" update_ssh_param "X11Forwarding" "no" "$sshd_config" update_ssh_param "MaxAuthTries" "3" "$sshd_config" update_ssh_param "ClientAliveInterval" "300" "$sshd_config" update_ssh_param "ClientAliveCountMax" "2" "$sshd_config" update_ssh_param "LoginGraceTime" "60" "$sshd_config" update_ssh_param "MaxSessions" "2" "$sshd_config" update_ssh_param "TCPKeepAlive" "no" "$sshd_config" update_ssh_param "AllowAgentForwarding" "no" "$sshd_config" update_ssh_param "AllowTcpForwarding" "no" "$sshd_config" update_ssh_param "LogLevel" "VERBOSE" "$sshd_config" update_ssh_param "Compression" "no" "$sshd_config" update_ssh_param "Protocol" "2" "$sshd_config" # Algorithmes sécurisés (s'assurer qu'ils n'existent pas déjà) sed -i '/^Ciphers /d' "$sshd_config" sed -i '/^MACs /d' "$sshd_config" sed -i '/^KexAlgorithms /d' "$sshd_config" cat >> "$sshd_config" << 'EOF' # Algorithmes cryptographiques sécurisés Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256 KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 EOF # Bannière update_ssh_param "Banner" "/etc/issue.net" "$sshd_config" # Test de la configuration avant application print_info "Test de la configuration SSH..." if sshd -t 2>&1 | tee -a "$LOG_FILE"; then print_success "Configuration SSH valide" # Redémarrer le service print_info "Redémarrage du service SSH..." if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then print_success "SSH durci avec succès" # Afficher les ports configurés local ports=$(grep "^Port" "$sshd_config" | awk '{print $2}' | tr '\n' ' ') print_info "Ports SSH actifs: $ports" if [[ "$ssh_port" == "22022" ]]; then print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_warning "⚠ TESTEZ IMMÉDIATEMENT dans un NOUVEAU terminal:" print_warning " ssh -p $ssh_port $(whoami)@$(hostname -I | awk '{print $1}')" print_warning " NE FERMEZ PAS cette session avant validation!" print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" elif detect_lxc; then print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_warning "⚠ Conteneur LXC - Port SSH: 22" print_warning " Port 22022 non disponible dans ce conteneur" print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" fi else print_error "Échec redémarrage SSH" print_error "Restauration de la configuration..." cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null return 1 fi else print_error "Configuration SSH invalide - vérifiez les logs" print_error "Restauration de la sauvegarde..." cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config return 1 fi mark_step_done "$step_name" } # ÉTAPE 14: Configuration bannières configure_banners() { local step_name="configure_banners" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 15: Configuration pare-feu basée sur ports détectés (déjà définie) # ÉTAPE 16: Configuration Fail2ban configure_fail2ban() { local step_name="configure_fail2ban" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration de Fail2ban" backup_file "/etc/fail2ban/jail.conf" # Déterminer le port SSH local ssh_port=$(get_ssh_port_to_use) # Configuration adaptée selon l'environnement if detect_container; then print_warning "Conteneur détecté - configuration Fail2ban limitée" cat > /etc/fail2ban/jail.local << EOF [DEFAULT] bantime = 1h findtime = 10m maxretry = 3 ignoreip = 127.0.0.1/8 ::1 backend = systemd # Mode conteneur - pas d'action iptables banaction = %(banaction_allports)s banaction_allports = iptables-multiport [sshd] enabled = true port = $ssh_port filter = sshd logpath = /var/log/auth.log maxretry = 5 bantime = 1h # Action désactivée en conteneur (pas d'accès iptables) action = %(action_)s EOF print_info "Fail2ban configuré en mode conteneur (logging seul, sans bannissement iptables)" else # Configuration complète pour système physique/VM cat > /etc/fail2ban/jail.local << EOF [DEFAULT] bantime = 1h findtime = 10m maxretry = 3 ignoreip = 127.0.0.1/8 ::1 backend = systemd [sshd] enabled = true port = $ssh_port filter = sshd logpath = /var/log/auth.log maxretry = 5 bantime = 1h [apache-auth] enabled = true port = http,https logpath = /var/log/apache*/*error.log maxretry = 3 EOF print_info "Fail2ban configuré en mode complet avec bannissement iptables" fi # Activer et démarrer le service systemctl enable fail2ban 2>&1 | tee -a "$LOG_FILE" || true if systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE"; then print_success "Fail2ban configuré et démarré" # Vérifier le statut sleep 2 if systemctl is-active --quiet fail2ban; then print_info "Statut Fail2ban: $(systemctl is-active fail2ban)" else print_warning "Fail2ban démarré mais statut incertain" fi else print_warning "Problème au démarrage de Fail2ban - vérifiez les logs: journalctl -xeu fail2ban" fi mark_step_done "$step_name" } # ÉTAPE 17: Suppression paquets inutiles remove_unneeded_packages() { local step_name="remove_unneeded_packages" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 18: Restriction permissions restrict_file_permissions() { local step_name="restrict_file_permissions" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 19: Désactivation modules noyau disable_risky_kernel_modules() { local step_name="disable_risky_kernel_modules" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Désactivation des modules noyau risqués" cat > /etc/modprobe.d/hardening-blacklist.conf << 'EOF' # Protocoles réseau non utilisés blacklist dccp install dccp /bin/true blacklist sctp install sctp /bin/true blacklist rds install rds /bin/true blacklist tipc install tipc /bin/true # Systèmes de fichiers non utilisés blacklist cramfs install cramfs /bin/true blacklist freevxfs 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 # FireWire (DMA attack) blacklist firewire-core install firewire-core /bin/true # Thunderbolt (DMA attack) 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" } # ÉTAPE 20: Configuration limites sécurité configure_security_limits() { local step_name="configure_security_limits" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration des limites de sécurité" backup_file "/etc/security/limits.conf" cat >> /etc/security/limits.conf << 'EOF' # Désactiver core dumps * hard core 0 # Limiter les processus * soft nproc 512 * hard nproc 1024 # Limiter les fichiers ouverts * soft nofile 65536 * hard nofile 65536 EOF print_success "Limites de sécurité configurées" mark_step_done "$step_name" } # ÉTAPE 21: Vérification intégrité paquets verify_packages_integrity() { local step_name="verify_packages_integrity" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Vérification de l'intégrité des paquets" print_info "Vérification avec debsums (peut prendre du temps)..." debsums -s 2>&1 | tee -a "$LOG_FILE" || print_warning "Certains paquets ont des erreurs" print_success "Vérification d'intégrité terminée" mark_step_done "$step_name" } # ÉTAPE 22: Configuration mises à jour auto configure_automatic_updates() { local step_name="configure_automatic_updates" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration des mises à jour automatiques" cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF' Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; "${distro_id}:${distro_codename}-updates"; }; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::MinimalSteps "true"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Automatic-Reboot-Time "04:00"; Unattended-Upgrade::Mail "root"; Unattended-Upgrade::MailReport "on-change"; EOF cat > /etc/apt/apt.conf.d/10periodic << 'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Unattended-Upgrade "1"; EOF print_success "Mises à jour automatiques configurées" mark_step_done "$step_name" } # ÉTAPE 23: Configuration tâche AIDE cron configure_aide_cron() { local step_name="configure_aide_cron" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration des vérifications AIDE planifiées" cat > /etc/cron.weekly/aide-check << 'EOF' #!/bin/bash LOGFILE="/var/log/aide-check-$(date +%Y%m%d).log" if command -v aide > /dev/null; then echo "=== Vérification AIDE $(date) ===" > "$LOGFILE" if /usr/bin/aide --check >> "$LOGFILE" 2>&1; then echo "Vérification réussie" >> "$LOGFILE" if grep -q "added\|changed\|removed" "$LOGFILE"; then mail -s "[AIDE] Changements détectés sur $(hostname)" root < "$LOGFILE" fi else echo "Échec vérification" >> "$LOGFILE" mail -s "[AIDE] ALERTE - Échec sur $(hostname)" root < "$LOGFILE" fi echo "=== Fin vérification $(date) ===" >> "$LOGFILE" fi EOF chmod 750 /etc/cron.weekly/aide-check print_success "Vérification AIDE hebdomadaire configurée" mark_step_done "$step_name" } # ÉTAPE 24: Durcissement bannière SMTP harden_smtp_banner() { local step_name="harden_smtp_banner" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 25: Durcissement services systemd harden_systemd_services() { local step_name="harden_systemd_services" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Durcissement services systemd" command -v systemd-analyze > /dev/null || { print_warning "systemd-analyze non disponible" mark_step_done "$step_name" return 0 } # Vérifier si on est dans un conteneur if detect_container; then print_warning "Conteneur détecté - durcissement systemd limité" print_info "Les namespaces systemd ne sont pas supportés dans les conteneurs LXC" print_info "Le durcissement systemd est ignoré pour éviter les erreurs de démarrage" # Nettoyer les éventuelles configurations précédentes qui pourraient causer des problèmes local services=(ssh sshd fail2ban chrony) for service in "${services[@]}"; do local unit="${service}.service" 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 pour compatibilité conteneur" mark_step_done "$step_name" return 0 fi # Configuration pour système physique/VM local services=(ssh sshd fail2ban chrony) local hardened=0 for service in "${services[@]}"; do local unit="${service}.service" # Vérifier si le service existe et est actif ou enabled systemctl list-unit-files | grep -q "^${unit}" || continue if ! systemctl is-enabled "$unit" > /dev/null 2>&1 && \ ! 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" # Configuration de sécurité adaptée selon le service if [[ "$service" == "ssh" || "$service" == "sshd" ]]; then # Configuration plus permissive pour SSH cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF [Service] NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=full ProtectHome=read-only ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes EOF else # Configuration standard pour les autres services 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 # Recharger la configuration systemd systemctl daemon-reload # Vérifier que les services critiques démarrent toujours print_info "Vérification des services critiques..." for service in ssh sshd; do local unit="${service}.service" 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" } # ÉTAPE 26: Configuration PAM avancée configure_advanced_pam() { local step_name="configure_advanced_pam" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Configuration PAM avancée" backup_file "/etc/pam.d/common-password" # S'assurer sha512 et rounds grep -q "pam_unix.so" /etc/pam.d/common-password && { grep -q "sha512" /etc/pam.d/common-password || \ sed -i 's/pam_unix.so.*/& sha512/' /etc/pam.d/common-password grep -q "rounds=" /etc/pam.d/common-password || \ sed -i 's/pam_unix.so.*/& rounds=500000/' /etc/pam.d/common-password } # Appliquer expiration mots de passe print_info "Application expiration mots de passe aux utilisateurs..." while IFS=: read -r user _ uid _ _ _ shell; do [[ "$uid" -ge 1000 || "$user" == "root" ]] && \ [[ -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" } # ÉTAPE 27: Vérification partitions check_partition_layout() { local step_name="check_partition_layout" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 28: Vérification fichiers noyau check_vmlinuz() { local step_name="check_vmlinuz" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } 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" } # ÉTAPE 29: Exécution chkrootkit run_chkrootkit() { local step_name="run_chkrootkit" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Exécution de chkrootkit" print_info "Scan rootkit en cours..." chkrootkit > "$BACKUP_DIR/chkrootkit_report.log" 2>&1 || \ print_warning "Chkrootkit a détecté des avertissements (voir $BACKUP_DIR/chkrootkit_report.log)" print_success "Scan chkrootkit terminé" mark_step_done "$step_name" } # ÉTAPE 30: Nettoyage port SSH 22 temporaire prepare_ssh_cleanup() { local step_name="prepare_ssh_cleanup" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Préparation nettoyage port SSH 22" # Vérifier si nous avons besoin de nettoyer le port 22 local ssh_port=$(get_ssh_port_to_use) if [[ "$ssh_port" == "22022" ]]; then 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" } # ÉTAPE 31: Audit Lynis final run_lynis_audit() { local step_name="run_lynis_audit" check_step_done "$step_name" && { skip_step "$step_name"; return 0; } print_step "Exécution de l'audit Lynis" print_info "Audit système complet en cours (peut prendre plusieurs minutes)..." # Exécuter Lynis et sauvegarder le rapport lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1 # Extraire le score de durcissement local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1) local max_score=100 # Score maximum standard Lynis # Si on ne trouve pas le score dans le rapport, utiliser une valeur par défaut [[ -z "$score" ]] && score=0 # Sauvegarder le score pour le résumé echo "$score" > "/tmp/lynis_score.txt" echo "$max_score" > "/tmp/lynis_max_score.txt" # Calculer le pourcentage pour la barre de progression local percentage=$((score * 100 / max_score)) local bar_length=50 # Longueur de la barre en caractères local filled_length=$((percentage * bar_length / 100)) local empty_length=$((bar_length - filled_length)) # Créer la barre de progression local bar="[" for ((i=0; i/dev/null || echo "0") local suggestions=$(grep -c "Suggestion" "$SECURITY_REPORT" 2>/dev/null || echo "0") local tests=$(grep -c "Test" "$SECURITY_REPORT" 2>/dev/null || echo "0") echo " • Tests effectués: $tests" echo " • Avertissements: $warnings" echo " • Suggestions: $suggestions" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # Afficher les principales suggestions si score < 80 if [[ $score -lt 80 ]]; then echo "" print_info "Top 5 des suggestions prioritaires:" echo "┌──────────────────────────────────────────────────────┐" grep "Suggestion" "$SECURITY_REPORT" | head -5 | nl -w1 -s'. ' | while read -r line; do echo "│ ${line}" done echo "└──────────────────────────────────────────────────────┘" fi # Afficher les warnings critiques echo "" if [[ $warnings -gt 0 ]]; then print_warning "⚠ Avertissements critiques détectés:" echo "┌──────────────────────────────────────────────────────┐" grep "Warning" "$SECURITY_REPORT" | head -3 | nl -w1 -s'. ' | while read -r line; do echo "│ ${line}" done echo "└──────────────────────────────────────────────────────┘" fi echo "" print_info "Pour voir le rapport complet: cat $SECURITY_REPORT" print_info "Pour voir les suggestions: grep Suggestion $SECURITY_REPORT" mark_step_done "$step_name" } # ============================================================================== # FONCTION NETTOYAGE SSH # ============================================================================== cleanup_ssh_port() { print_step "Nettoyage définitif port SSH 22" echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ ⚠ ATTENTION CRITIQUE ⚠ ║${NC}" echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}" echo "" # Vérifier si on est dans un conteneur LXC avec port 22 seulement if detect_lxc && ! is_port_open "22022"; then print_warning "Conteneur LXC détecté - port 22022 non disponible" print_info "Le port 22 restera actif dans ce conteneur" echo "" echo -n "Continuer malgré tout ? (oui/non): " read -r confirmation [[ "$confirmation" != "oui" ]] && { print_info "Annulation - port 22 maintenu" return 0 } fi echo "Cette action va supprimer définitivement l'accès SSH sur le port 22." echo "Assurez-vous d'avoir testé SSH sur le port $NEW_SSH_PORT." echo "" echo -n "Confirmez-vous avoir testé avec succès SSH:$NEW_SSH_PORT ? (oui/non): " read -r confirmation [[ "$confirmation" != "oui" ]] && { print_error "Annulation. Testez d'abord: ssh -p $NEW_SSH_PORT user@$(hostname -I | awk '{print $1}')" return 1 } backup_file "/etc/ssh/sshd_config" sed -i '/^Port 22$/d' /etc/ssh/sshd_config # Supprimer la règle UFW seulement si UFW est actif if systemctl is-active --quiet ufw 2>/dev/null; then ufw delete allow 22/tcp 2>/dev/null || true fi sshd -t && { systemctl restart sshd print_success "Port SSH 22 supprimé" print_info "SSH disponible uniquement sur le port $NEW_SSH_PORT" } || { print_error "Erreur configuration - restauration" cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config systemctl restart sshd return 1 } } # ============================================================================== # FONCTIONS PRINCIPALES # ============================================================================== check_requirements() { print_info "Vérification des prérequis..." [[ $EUID -ne 0 ]] && { print_error "Ce script doit être exécuté en tant que root" exit 1 } [[ ! -f /etc/debian_version ]] && \ print_warning "Système non Debian/Ubuntu - compatibilité limitée" mkdir -p "$BACKUP_DIR" touch "$LOG_FILE" "$STATUS_FILE" 2>/dev/null || { print_error "Impossible de créer les fichiers de log" exit 1 } print_success "Prérequis vérifiés" } print_header() { clear echo -e "${CYAN}" echo "╔══════════════════════════════════════════════════════════════════╗" echo "║ SCRIPT DE DURCISSEMENT SYSTÈME OPTIMISÉ v7.0 ║" echo "║ Sécurité Debian/Ubuntu ║" echo "║ avec détection automatique des ports ║" echo "╚══════════════════════════════════════════════════════════════════╝" echo -e "${NC}" echo -e "${BLUE}Informations système:${NC}" echo " • Distribution: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"')" echo " • Noyau: $(uname -r)" echo " • Hostname: $(hostname)" echo " • Type: $(detect_container && echo "Conteneur/LXC" || echo "Hôte physique/VM")" echo "" echo -e "${BLUE}Configuration:${NC}" echo " • Port SSH nouveau: $NEW_SSH_PORT" echo " • Détection ports: Activée" echo " • Fichier log: $LOG_FILE" echo " • Sauvegardes: $BACKUP_DIR" echo " • Rapport final: $SECURITY_REPORT" echo "" echo -e "${YELLOW}⚠ Ce script va modifier profondément la configuration système ⚠${NC}" echo "" read -p "Appuyez sur Entrée pour continuer ou Ctrl+C pour annuler..." } print_summary() { echo -e "\n${GREEN}" echo "╔══════════════════════════════════════════════════════════════════╗" echo "║ DURCISSEMENT SYSTÈME TERMINÉ AVEC SUCCÈS ║" echo "╚══════════════════════════════════════════════════════════════════╝" echo -e "${NC}" # Récupérer le score Lynis local lynis_score="N/A" local lynis_max_score=100 if [[ -f "/tmp/lynis_score.txt" ]]; then lynis_score=$(cat /tmp/lynis_score.txt) [[ -f "/tmp/lynis_max_score.txt" ]] && lynis_max_score=$(cat /tmp/lynis_max_score.txt) elif [[ -f "$SECURITY_REPORT" ]]; then lynis_score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1) [[ -z "$lynis_score" ]] && lynis_score="N/A" fi # Afficher le score avec barre de progression dans le résumé if [[ "$lynis_score" != "N/A" ]]; then # Calcul de la barre de progression local percentage=$((lynis_score * 100 / lynis_max_score)) local bar_length=25 # Barre plus courte pour le résumé local filled_length=$((percentage * bar_length / 100)) local empty_length=$((bar_length - filled_length)) local bar="[" for ((i=0; i/dev/null || echo "désactivé (conteneur)")" echo " • Fail2ban: $(systemctl is-active fail2ban 2>/dev/null || echo "inactif")" echo " • AIDE: Base initialisée + vérification hebdomadaire" echo " • ClamAV: $(systemctl is-active clamav-freshclam 2>/dev/null || echo "inactif")" echo " • Chrony: $(systemctl is-active chrony 2>/dev/null || echo "inactif (conteneur)")" echo "" # Afficher les ports détectés et configurés if [[ -f "$OPEN_PORTS_FILE" ]]; then echo -e "${YELLOW}🌐 PORTS DÉTECTÉS ET CONFIGURÉS:${NC}" echo " • Ports ouverts détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun")" if systemctl is-active --quiet ufw 2>/dev/null; then echo " • Règles UFW appliquées: $(ufw status | grep -c 'ALLOW') règle(s)" fi echo "" fi echo -e "${YELLOW}📁 FICHIERS GÉNÉRÉS:${NC}" echo " • Log complet: $LOG_FILE" echo " • Sauvegardes: $BACKUP_DIR" echo " • Rapport Lynis: $SECURITY_REPORT" echo " • Rapport chkrootkit: $BACKUP_DIR/chkrootkit_report.log" echo " • Ports détectés: $OPEN_PORTS_FILE" echo "" # Statistiques Lynis détaillées if [[ -f "$SECURITY_REPORT" ]]; then local warnings=$(grep -c "Warning" "$SECURITY_REPORT" 2>/dev/null || echo "0") local suggestions=$(grep -c "Suggestion" "$SECURITY_REPORT" 2>/dev/null || echo "0") echo -e "${YELLOW}📈 STATISTIQUES LYNIS:${NC}" echo " • Avertissements: $warnings" echo " • Suggestions d'amélioration: $suggestions" echo "" fi detect_container && { echo -e "${YELLOW}📦 SPÉCIFICITÉS CONTENEUR:${NC}" echo " • Process Accounting: Désactivé (géré par hôte)" echo " • UFW/Pare-feu: Géré par l'hôte Proxmox" echo " • Modules noyau: Gérés par l'hôte" echo " • Partitions: Gérées par Proxmox" echo " • Chrony: Synchronisation horaire gérée par l'hôte" if detect_lxc && ! is_port_open "22022"; then echo " • SSH: Port 22 maintenu (port 22022 non disponible en LXC)" fi echo "" } echo -e "${YELLOW}🔍 COMMANDES UTILES POST-INSTALLATION:${NC}" echo " 1. État Fail2ban: sudo fail2ban-client status sshd" echo " 2. Logs SSH: sudo tail -f /var/log/auth.log" echo " 3. Ports ouverts: sudo ss -tlnp" echo " 4. Règles UFW: sudo ufw status verbose" echo " 5. Score Lynis: grep 'Hardening index' $SECURITY_REPORT" echo " 6. Suggestions: grep 'Suggestion' $SECURITY_REPORT" echo " 7. Rapport complet: cat $SECURITY_REPORT | less" echo "" # Interpréter le score if [[ "$lynis_score" != "N/A" ]]; then echo -e "${CYAN}💡 INTERPRÉTATION DU SCORE:${NC}" if [[ $lynis_score -ge 80 ]]; then echo " ✓ Excellente sécurité ! Votre système est bien durci." echo " → Continuez à surveiller et à appliquer les mises à jour." elif [[ $lynis_score -ge 60 ]]; then echo " ✓ Bonne sécurité, mais des améliorations sont possibles." echo " → Consultez les suggestions Lynis pour progresser." elif [[ $lynis_score -ge 40 ]]; then echo " ⚠ Sécurité moyenne, améliorations recommandées." echo " → Examinez attentivement les avertissements et suggestions." else echo " ⚠ Score faible - plusieurs améliorations nécessaires." echo " → Priorisez les warnings critiques de Lynis." fi echo "" fi if [[ "$ssh_port" == "22022" ]]; then echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" echo -e "${RED}⚠ TESTEZ SSH:22022 IMMÉDIATEMENT AVANT TOUTE AUTRE ACTION ⚠${NC}" echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}" fi # Nettoyage rm -f /tmp/lynis_score.txt /tmp/lynis_max_score.txt 2>/dev/null || true } main() { # Mode nettoyage SSH [[ $# -eq 1 && "$1" == "--cleanup-ssh" ]] && { cleanup_ssh_port return 0 } # Initialisation check_requirements print_header log_message "==================================================" "START" log_message "Démarrage durcissement système v7.0" "START" log_message "Système: $(hostname)" "START" log_message "Type: $(detect_container && echo "Conteneur" || echo "Physique/VM")" "START" log_message "==================================================" "START" # EXÉCUTION DES ÉTAPES install_security_tools 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 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 # Résumé print_summary log_message "==================================================" "END" log_message "Durcissement terminé - Durée: $((SECONDS / 60)) min" "END" log_message "Ports détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun")" "END" log_message "==================================================" "END" } # Gestion signaux trap 'print_error "Script interrompu"; exit 130' INT TERM # Point d'entrée [[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"