system_hardering/system_hardening_optimized.sh

1614 lines
59 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: 6.0
# Date: $(date +%Y-%m-%d)
# Author: Security Team
# Description: Système de durcissement sécurité pour Debian/Ubuntu LTS
# 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
TOTAL_STEPS=30
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 ]]
}
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 DURCISSEMENT
# ==============================================================================
# É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: 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 3: 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 4: 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 5: 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 6: 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 7: 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 8: 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 9: 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 10: 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 11: 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 12: Durcissement SSH
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"
}
# Configuration des ports
sed -i '/^Port /d' "$sshd_config"
sed -i '/^#Port /d' "$sshd_config"
echo "Port 22" >> "$sshd_config"
echo "Port $NEW_SSH_PORT" >> "$sshd_config"
# 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"
print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_warning "⚠ TESTEZ IMMÉDIATEMENT dans un NOUVEAU terminal:"
print_warning " ssh -p $NEW_SSH_PORT $(whoami)@$(hostname -I | awk '{print $1}')"
print_warning " NE FERMEZ PAS cette session avant validation!"
print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
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 13: 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 14: Configuration UFW
configure_ufw_firewall() {
local step_name="configure_ufw_firewall"
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
print_step "Configuration du pare-feu UFW"
# 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"
print_info "Les règles de pare-feu doivent être configurées au niveau de l'hôte"
# 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
# Ports SSH
print_info "Autorisation des ports SSH..."
ufw allow 22/tcp comment 'SSH temporaire' || print_warning "Erreur ajout règle SSH:22"
ufw allow "${NEW_SSH_PORT}/tcp" comment 'SSH sécurisé' || print_warning "Erreur ajout règle SSH:${NEW_SSH_PORT}"
# Limiter ICMP
print_info "Configuration limitation ICMP..."
if [[ -f /etc/ufw/before.rules ]]; then
backup_file "/etc/ufw/before.rules"
# Vérifier si la règle n'existe pas déjà
if ! grep -q "Limiter pings" /etc/ufw/before.rules; then
cat >> /etc/ufw/before.rules << 'EOF'
# Limiter pings
-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 3/second --limit-burst 5 -j ACCEPT
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP
EOF
fi
fi
# Activer UFW
print_info "Activation du pare-feu..."
if echo "y" | ufw --force enable > /dev/null 2>&1; then
print_success "Pare-feu UFW configuré et activé"
else
# Fallback
if ufw enable <<< 'y' > /dev/null 2>&1; then
print_success "Pare-feu UFW configuré et activé (fallback)"
else
print_warning "Échec activation UFW - continuer sans..."
fi
fi
mark_step_done "$step_name"
}
# ÉTAPE 15: 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"
# 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 = 22,${NEW_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 = 22,${NEW_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 16: 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 17: 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 18: 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 19: 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 20: 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 21: 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 22: 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 23: 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 24: 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 25: 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 26: 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 27: 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 28: 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 29: 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"
print_warning "Port SSH 22 toujours actif (temporaire)"
print_info "Après avoir testé SSH sur le port $NEW_SSH_PORT:"
print_info "Exécutez: $0 --cleanup-ssh"
mark_step_done "$step_name"
}
# ÉTAPE 30: 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 ""
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
ufw delete allow 22/tcp 2>/dev/null || true
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É v6.0 ║"
echo "║ Sécurité Debian/Ubuntu ║"
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 " • 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
echo -e "${RED}🔐 ACTIONS CRITIQUES IMMÉDIATES:${NC}"
echo -e " 1. ${RED}TESTEZ SSH MAINTENANT:${NC}"
echo " → ssh -p $NEW_SSH_PORT $(whoami)@$(hostname -I | awk '{print $1}')"
echo -e "${RED}NE FERMEZ PAS CETTE SESSION AVANT VALIDATION !${NC}"
echo ""
echo " 2. Une fois SSH:$NEW_SSH_PORT validé:"
echo " → sudo $0 --cleanup-ssh"
echo ""
echo -e "${YELLOW}📊 SERVICES CONFIGURÉS:${NC}"
echo " • SSH: Ports 22 (temporaire) + $NEW_SSH_PORT (permanent)"
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 ""
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 ""
# 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"
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
echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}"
echo -e "${RED}⚠ TESTEZ SSH:$NEW_SSH_PORT IMMÉDIATEMENT AVANT TOUTE AUTRE ACTION ⚠${NC}"
echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}"
# 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 v6.0" "START"
log_message "Système: $(hostname)" "START"
log_message "==================================================" "START"
# EXÉCUTION DES ÉTAPES
install_security_tools
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_ufw_firewall
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 "==================================================" "END"
}
# Gestion signaux
trap 'print_error "Script interrompu"; exit 130' INT TERM
# Point d'entrée
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"