system_hardering/system_hardening_optimized.sh

2085 lines
80 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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<filled_length; i++)); do
bar+="█"
done
for ((i=0; i<empty_length; i++)); do
bar+="░"
done
bar+="]"
# Couleur selon le score
local color=$RED
[[ $score -ge 80 ]] && color=$GREEN
[[ $score -ge 60 && $score -lt 80 ]] && color=$YELLOW
print_success "Audit Lynis terminé"
# Afficher le score avec la barre de progression
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " SCORE LYNIS FINAL"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "Score de durcissement: ${color}${bar} ${score}/${max_score}${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
print_info "Rapport complet: $SECURITY_REPORT"
# Extraire et afficher les statistiques
echo ""
print_info "Statistiques de l'audit:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local warnings=$(grep -c "Warning" "$SECURITY_REPORT" 2>/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<filled_length; i++)); do
bar+="█"
done
for ((i=0; i<empty_length; i++)); do
bar+="░"
done
bar+="]"
# Déterminer la couleur du score
local score_color=$RED
[[ $lynis_score -ge 80 ]] && score_color=$GREEN
[[ $lynis_score -ge 60 && $lynis_score -lt 80 ]] && score_color=$YELLOW
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " SCORE LYNIS FINAL"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "Score de durcissement: ${score_color}${bar} ${lynis_score}/${lynis_max_score}${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
fi
echo -e "\n${MAGENTA}════════════════════ RÉSUMÉ FINAL ════════════════════${NC}\n"
# Déterminer la couleur du score pour le résumé
local score_color=$RED
if [[ "$lynis_score" != "N/A" ]]; then
[[ $lynis_score -ge 80 ]] && score_color=$GREEN
[[ $lynis_score -ge 60 && $lynis_score -lt 80 ]] && score_color=$YELLOW
echo -e "${score_color}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${score_color}║ 🏆 SCORE DE SÉCURITÉ FINAL ║${NC}"
echo -e "${score_color}║ ║${NC}"
printf "${score_color}║ %3s / 100 points ║${NC}\n" "$lynis_score"
if [[ $lynis_score -ge 80 ]]; then
echo -e "${score_color}║ ★★★★★ Excellent ║${NC}"
elif [[ $lynis_score -ge 60 ]]; then
echo -e "${score_color}║ ★★★☆☆ Bon ║${NC}"
elif [[ $lynis_score -ge 40 ]]; then
echo -e "${score_color}║ ★★☆☆☆ Moyen ║${NC}"
else
echo -e "${score_color}║ ★☆☆☆☆ À améliorer ║${NC}"
fi
echo -e "${score_color}║ ║${NC}"
echo -e "${score_color}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
fi
# Déterminer le port SSH utilisé
local ssh_port=$(get_ssh_port_to_use)
echo -e "${RED}🔐 ACTIONS CRITIQUES IMMÉDIATES:${NC}"
echo -e " 1. ${RED}TESTEZ SSH MAINTENANT:${NC}"
echo " → ssh -p $ssh_port $(whoami)@$(hostname -I | awk '{print $1}')"
if [[ "$ssh_port" == "22022" ]]; then
echo -e "${RED}NE FERMEZ PAS CETTE SESSION AVANT VALIDATION !${NC}"
echo ""
echo " 2. Une fois SSH:$ssh_port validé:"
echo " → sudo $0 --cleanup-ssh"
else
echo ""
echo " 2. Conteneur LXC - Port SSH: $ssh_port"
echo " → Aucun nettoyage nécessaire"
fi
echo ""
echo -e "${YELLOW}📊 SERVICES CONFIGURÉS:${NC}"
echo " • SSH: Port $ssh_port"
if [[ "$ssh_port" == "22022" ]]; then
echo " (Port 22 temporairement actif)"
fi
echo " • UFW: $(systemctl is-active ufw 2>/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 "$@"