system_hardering/system_hardening_optimized.sh

2470 lines
89 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: 8.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 et contrôle des étapes
# 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
# Variables de contrôle
FORCE_ALL=false
FORCE_STEPS=()
SKIP_STEPS=()
RESET_ALL=false
LIST_STEPS=false
SHOW_STATUS=false
# ==============================================================================
# COULEURS
# ==============================================================================
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly CYAN='\033[0;36m'
readonly BLUE='\033[0;34m'
readonly MAGENTA='\033[0;35m'
readonly NC='\033[0m'
# ==============================================================================
# DÉFINITION DES ÉTAPES
# ==============================================================================
declare -A STEP_DESCRIPTIONS=(
["install_security_tools"]="Installation des outils de sécurité"
["detect_open_ports"]="Détection des ports ouverts"
["configure_process_accounting"]="Configuration Process Accounting"
["configure_sysctl_security"]="Durcissement sysctl"
["configure_log_permissions"]="Permissions des logs"
["configure_pam_password_policy"]="Politique mots de passe PAM"
["configure_login_defs"]="Configuration login.defs"
["configure_umask"]="Configuration umask"
["configure_aide_sha512"]="Configuration AIDE SHA512"
["initialize_aide_db"]="Initialisation base AIDE"
["configure_clamav"]="Configuration ClamAV"
["configure_chrony"]="Configuration Chrony"
["harden_ssh"]="Durcissement SSH"
["configure_banners"]="Configuration bannières"
["configure_firewall_ports"]="Configuration pare-feu UFW"
["configure_fail2ban"]="Configuration Fail2ban"
["remove_unneeded_packages"]="Suppression paquets inutiles"
["restrict_file_permissions"]="Restriction permissions fichiers"
["disable_risky_kernel_modules"]="Désactivation modules noyau"
["configure_security_limits"]="Configuration limites sécurité"
["verify_packages_integrity"]="Vérification intégrité paquets"
["configure_automatic_updates"]="Configuration mises à jour auto"
["configure_aide_cron"]="Configuration tâche AIDE cron"
["harden_smtp_banner"]="Durcissement bannière SMTP"
["harden_systemd_services"]="Durcissement services systemd"
["configure_advanced_pam"]="Configuration PAM avancée"
["check_partition_layout"]="Vérification partitions"
["check_vmlinuz"]="Vérification fichiers noyau"
["run_chkrootkit"]="Exécution chkrootkit"
["prepare_ssh_cleanup"]="Préparation nettoyage SSH"
["run_lynis_audit"]="Audit Lynis final"
)
# ==============================================================================
# FONCTIONS UTILITAIRES
# ==============================================================================
log_message() {
local message="$1"
local level="${2:-INFO}"
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
}
print_step() {
local step_title="$1"
echo -e "\n${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${YELLOW} ÉTAPE ${CURRENT_STEP}/${TOTAL_STEPS}: $step_title${NC}"
echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}"
log_message "Début étape $CURRENT_STEP: $step_title" "STEP"
CURRENT_STEP=$((CURRENT_STEP + 1))
}
print_success() {
echo -e "${GREEN}${NC} $1"
log_message "$1" "SUCCESS"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
log_message "$1" "WARNING"
}
print_error() {
echo -e "${RED}${NC} $1" >&2
log_message "$1" "ERROR"
}
print_info() {
echo -e "${BLUE}${NC} $1"
log_message "$1" "INFO"
}
# ==============================================================================
# GESTION DES ÉTAPES
# ==============================================================================
check_step_done() {
local step_name="$1"
if $FORCE_ALL; then
return 1
fi
if [[ " ${FORCE_STEPS[@]} " =~ " ${step_name} " ]]; then
print_info "Forçage de l'étape: $step_name"
return 1
fi
if [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then
print_info "Saut de l'étape: $step_name"
return 0
fi
grep -q "^${step_name}$" "$STATUS_FILE" 2>/dev/null
}
mark_step_done() {
local step_name="$1"
if [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then
print_info "Étape $step_name ignorée - non marquée comme terminée"
return 0
fi
sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true
echo "$step_name" >> "$STATUS_FILE"
log_message "Étape '$step_name' marquée comme terminée" "STATUS"
}
skip_step() {
local step_name="$1"
print_info "Étape déjà effectuée : $step_name"
CURRENT_STEP=$((CURRENT_STEP + 1))
}
detect_container() {
grep -qE "lxc|container" /proc/self/cgroup 2>/dev/null || \
[[ -f /.dockerenv ]] || [[ -d /dev/lxc ]]
}
detect_lxc() {
grep -q "lxc" /proc/self/cgroup 2>/dev/null || [[ -d /dev/lxc ]]
}
backup_file() {
local file_path="$1"
[[ -f "$file_path" ]] && {
mkdir -p "$BACKUP_DIR"
cp "$file_path" "${BACKUP_DIR}/"
log_message "Sauvegarde de $file_path" "BACKUP"
}
}
add_unique_line() {
local line="$1"
local file="$2"
grep -qF "$line" "$file" 2>/dev/null || {
echo "$line" >> "$file"
log_message "Ajout ligne: '$line' -> $file" "CONFIG"
}
}
update_config_value() {
local file="$1"
local key="$2"
local value="$3"
backup_file "$file"
if grep -q "^${key}" "$file"; then
sed -i "s/^${key}.*/${key} ${value}/" "$file"
else
echo "${key} ${value}" >> "$file"
fi
}
# ==============================================================================
# FONCTIONS DE CONTRÔLE
# ==============================================================================
reset_step() {
local step_name="$1"
if [[ -f "$STATUS_FILE" ]]; then
sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true
print_success "Étape '$step_name' réinitialisée"
case $step_name in
"harden_ssh")
if [[ -f "${BACKUP_DIR}/sshd_config" ]]; then
read -p "Restaurer la configuration SSH originale ? (o/N): " -r restore_ssh
if [[ "$restore_ssh" =~ ^[Oo]$ ]]; then
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || true
print_success "Configuration SSH restaurée"
fi
fi
;;
"configure_firewall_ports")
if command -v ufw > /dev/null 2>&1; then
read -p "Réinitialiser les règles UFW ? (o/N): " -r reset_ufw
if [[ "$reset_ufw" =~ ^[Oo]$ ]]; then
ufw --force reset > /dev/null 2>&1 || true
print_success "Règles UFW réinitialisées"
fi
fi
;;
"configure_fail2ban")
if [[ -f "${BACKUP_DIR}/jail.local" ]]; then
read -p "Restaurer la configuration Fail2ban originale ? (o/N): " -r restore_fail2ban
if [[ "$restore_fail2ban" =~ ^[Oo]$ ]]; then
cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true
systemctl restart fail2ban 2>/dev/null || true
print_success "Configuration Fail2ban restaurée"
fi
fi
;;
esac
else
print_warning "Fichier de statut non trouvé"
fi
}
reset_all_steps() {
print_step "Réinitialisation de toutes les étapes"
echo -e "${RED}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ⚠ ATTENTION CRITIQUE ⚠ ║${NC}"
echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "Cette action va:"
echo "1. Supprimer l'historique d'exécution de toutes les étapes"
echo "2. Permettre une ré-exécution complète du script"
echo "3. NE supprime PAS les configurations appliquées"
echo ""
echo -n "Confirmez-vous la réinitialisation complète ? (oui/NON): "
read -r confirmation
if [[ "$confirmation" == "oui" ]]; then
if [[ -f "$STATUS_FILE" ]]; then
cp "$STATUS_FILE" "${STATUS_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
print_info "Ancien statut sauvegardé"
fi
> "$STATUS_FILE"
print_success "Toutes les étapes ont été réinitialisées"
print_info "Vous pouvez maintenant ré-exécuter le script"
echo ""
read -p "Voulez-vous restaurer les configurations originales ? (o/N): " -r restore_all
if [[ "$restore_all" =~ ^[Oo]$ ]]; then
echo ""
echo "Sélectionnez les configurations à restaurer:"
echo "1) SSH"
echo "2) UFW (pare-feu)"
echo "3) Fail2ban"
echo "4) Toutes les configurations"
echo "5) Aucune"
echo ""
read -p "Choix (1-5): " -r restore_choice
case $restore_choice in
1)
restore_ssh_config
;;
2)
restore_ufw_config
;;
3)
restore_fail2ban_config
;;
4)
restore_all_configs
;;
*)
print_info "Aucune restauration effectuée"
;;
esac
fi
else
print_error "Réinitialisation annulée"
exit 1
fi
}
restore_ssh_config() {
if [[ -f "${BACKUP_DIR}/sshd_config" ]]; then
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || true
print_success "Configuration SSH restaurée"
else
print_warning "Sauvegarde SSH non trouvée"
fi
}
restore_ufw_config() {
if command -v ufw > /dev/null 2>&1; then
ufw --force reset > /dev/null 2>&1 || true
print_success "Configuration UFW réinitialisée"
fi
}
restore_fail2ban_config() {
if [[ -f "${BACKUP_DIR}/jail.local" ]]; then
cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true
systemctl restart fail2ban 2>/dev/null || true
print_success "Configuration Fail2ban restaurée"
fi
}
restore_all_configs() {
restore_ssh_config
restore_ufw_config
restore_fail2ban_config
}
list_all_steps() {
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ LISTE DES ÉTAPES DISPONIBLES ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
local i=1
for step_name in "${!STEP_DESCRIPTIONS[@]}"; do
local status="❌ Non exécutée"
if check_step_done "$step_name" 2>/dev/null; then
status="✅ Terminée"
fi
printf "${BLUE}%2d.${NC} %-35s ${GREEN}%-12s${NC}\n" \
"$i" "${STEP_DESCRIPTIONS[$step_name]}" "$status"
i=$((i + 1))
done
echo ""
echo -e "${YELLOW}COMMANDES DE CONTRÔLE:${NC}"
echo " --force-step=NOM Forcer une étape spécifique"
echo " --force-all Forcer toutes les étapes"
echo " --skip-step=NOM Sauter une étape spécifique"
echo " --reset-step=NOM Réinitialiser une étape"
echo " --reset-all Réinitialiser toutes les étapes"
echo " --list-steps Lister toutes les étapes"
echo " --show-status Afficher le statut des étapes"
echo ""
if [[ -f "$STATUS_FILE" ]]; then
local completed_count=$(wc -l < "$STATUS_FILE" 2>/dev/null || echo 0)
echo -e "${GREEN}Étapes terminées: $completed_count/${#STEP_DESCRIPTIONS[@]}${NC}"
fi
}
show_step_status() {
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ STATUT DES ÉTAPES ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
local completed=0
local total=${#STEP_DESCRIPTIONS[@]}
local step_order=(
"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"
)
for step_name in "${step_order[@]}"; do
local description="${STEP_DESCRIPTIONS[$step_name]}"
local status_color=$RED
local status_icon="❌"
local status_text="Non exécutée"
if check_step_done "$step_name" 2>/dev/null; then
status_color=$GREEN
status_icon="✅"
status_text="Terminée"
completed=$((completed + 1))
fi
local extra_info=""
if [[ " ${FORCE_STEPS[@]} " =~ " ${step_name} " ]]; then
extra_info=" [FORCÉ]"
elif [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then
extra_info=" [IGNORÉ]"
fi
echo -e "${status_icon} ${status_color}${description}${NC}${extra_info}"
done
echo ""
echo -e "${YELLOW}RÉSUMÉ:${NC}"
echo -e " Progression: ${completed}/${total} étapes"
local width=50
local percent=$((completed * 100 / total))
local filled=$((percent * width / 100))
local empty=$((width - filled))
echo -n " ["
for ((i=0; i<filled; i++)); do echo -n "█"; done
for ((i=0; i<empty; i++)); do echo -n "░"; done
echo "] ${percent}%"
echo ""
if [[ -f "$STATUS_FILE" ]]; then
echo -e "${BLUE}Dernière modification:${NC} $(stat -c %y "$STATUS_FILE" 2>/dev/null || echo "Inconnu")"
fi
}
# ==============================================================================
# PARSING DES ARGUMENTS
# ==============================================================================
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
--force-all)
FORCE_ALL=true
print_warning "Mode FORCE ALL activé - Toutes les étapes seront ré-exécutées"
shift
;;
--force-step=*)
local step="${1#*=}"
FORCE_STEPS+=("$step")
print_info "Étape forcée: $step"
shift
;;
--skip-step=*)
local step="${1#*=}"
SKIP_STEPS+=("$step")
print_info "Étape ignorée: $step"
shift
;;
--reset-step=*)
local step="${1#*=}"
reset_step "$step"
exit 0
;;
--reset-all)
reset_all_steps
exit 0
;;
--list-steps)
LIST_STEPS=true
shift
;;
--show-status)
SHOW_STATUS=true
shift
;;
--cleanup-ssh)
shift
;;
--help|-h)
show_help
exit 0
;;
*)
print_error "Option inconnue: $1"
show_help
exit 1
;;
esac
done
}
show_help() {
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ AIDE - SYSTEM HARDENING SCRIPT v8.0 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${GREEN}USAGE:${NC}"
echo " $0 [OPTIONS]"
echo ""
echo -e "${GREEN}OPTIONS:${NC}"
echo " --force-all Forcer l'exécution de toutes les étapes"
echo " --force-step=NOM Forcer une étape spécifique"
echo " --skip-step=NOM Sauter une étape spécifique"
echo " --reset-step=NOM Réinitialiser une étape (la marquer comme non faite)"
echo " --reset-all Réinitialiser toutes les étapes"
echo " --list-steps Lister toutes les étapes disponibles"
echo " --show-status Afficher le statut des étapes"
echo " --cleanup-ssh Nettoyer le port SSH 22 (après test)"
echo " --help, -h Afficher cette aide"
echo ""
echo -e "${YELLOW}EXEMPLES:${NC}"
echo " # Exécution normale"
echo " $0"
echo ""
echo " # Forcer uniquement la configuration SSH"
echo " $0 --force-step=harden_ssh"
echo ""
echo " # Forcer SSH et UFW, sauter Fail2ban"
echo " $0 --force-step=harden_ssh --force-step=configure_firewall_ports --skip-step=configure_fail2ban"
echo ""
echo " # Réinitialiser uniquement la configuration SSH"
echo " $0 --reset-step=harden_ssh"
echo ""
echo " # Voir le statut des étapes"
echo " $0 --show-status"
echo ""
echo -e "${RED}⚠ AVERTISSEMENT:${NC}"
echo " Le forçage d'étapes peut écraser des configurations existantes."
echo " Assurez-vous d'avoir des sauvegardes avant d'utiliser --force-all."
}
# ==============================================================================
# FONCTIONS DE DÉTECTION DES PORTS
# ==============================================================================
detect_open_ports() {
local step_name="detect_open_ports"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Détection des ports ouverts"
print_info "Scan des ports en écoute localement..."
ss -tlnp | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" 2>/dev/null || {
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
echo "22022"
fi
}
# ==============================================================================
# FONCTIONS DE DURCISSEMENT
# ==============================================================================
install_security_tools() {
local step_name="install_security_tools"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Mise à jour système et installation outils de sécurité"
print_info "Mise à jour des dépôts..."
DEBIAN_FRONTEND=noninteractive apt-get update -qq
print_info "Mise à jour du système..."
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq
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"
}
configure_process_accounting() {
local step_name="configure_process_accounting"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration du Process Accounting"
if detect_container; then
print_warning "Conteneur détecté - Process Accounting désactivé"
for service in acct.service psacct.service; do
systemctl list-unit-files | grep -q "^${service}" && {
systemctl stop "$service" 2>/dev/null || true
systemctl disable "$service" 2>/dev/null || true
systemctl mask "$service" 2>/dev/null || true
}
done
else
systemctl enable acct.service 2>/dev/null && \
systemctl start acct.service 2>/dev/null && \
print_success "Process Accounting activé" || \
print_warning "Process Accounting non disponible"
fi
mark_step_done "$step_name"
}
configure_sysctl_security() {
local step_name="configure_sysctl_security"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement des paramètres noyau (sysctl)"
cat > /etc/sysctl.d/99-security-hardening.conf << 'EOF'
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_syncookies = 1
net.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
kernel.randomize_va_space = 2
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.yama.ptrace_scope = 1
kernel.unprivileged_bpf_disabled = 1
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"
}
configure_log_permissions() {
local step_name="configure_log_permissions"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des permissions des fichiers de log"
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"
}
configure_pam_password_policy() {
local step_name="configure_pam_password_policy"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de la politique de mots de passe PAM"
backup_file "/etc/security/pwquality.conf"
cat >> /etc/security/pwquality.conf << 'EOF'
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"
sed -i 's/pam_unix.so.*/& remember=5 sha512 rounds=500000/' /etc/pam.d/common-password
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"
}
configure_login_defs() {
local step_name="configure_login_defs"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des paramètres de connexion (login.defs)"
backup_file "/etc/login.defs"
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"
}
configure_umask() {
local step_name="configure_umask"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de l'umask par défaut"
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"
}
configure_aide_sha512() {
local step_name="configure_aide_sha512"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
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"
}
initialize_aide_db() {
local step_name="initialize_aide_db"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Initialisation de la base de données AIDE"
print_info "Création de la base de données de référence..."
if 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"
}
configure_clamav() {
local step_name="configure_clamav"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
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"
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"
}
configure_chrony() {
local step_name="configure_chrony"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de la synchronisation horaire (Chrony)"
if detect_container; then
print_warning "Conteneur détecté - synchronisation horaire gérée par l'hôte"
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é"
fi
mark_step_done "$step_name"
return 0
fi
backup_file "/etc/chrony/chrony.conf"
timedatectl set-timezone Europe/Paris || {
print_warning "Impossible de définir le fuseau horaire"
}
cat > /etc/chrony/chrony.conf << 'EOF'
pool 2.debian.pool.ntp.org iburst
server 0.fr.pool.ntp.org iburst
server 1.fr.pool.ntp.org iburst
driftfile /var/lib/chrony/chrony.drift
logdir /var/log/chrony
makestep 1.0 3
rtcsync
EOF
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"
fi
mark_step_done "$step_name"
}
harden_ssh() {
local step_name="harden_ssh"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement du service SSH"
backup_file "/etc/ssh/sshd_config"
local sshd_config="/etc/ssh/sshd_config"
update_ssh_param() {
local param="$1"
local value="$2"
local file="$3"
sed -i "/^#*${param}/d" "$file"
echo "${param} ${value}" >> "$file"
}
local ssh_port=$(get_ssh_port_to_use)
sed -i '/^Port /d' "$sshd_config"
sed -i '/^#Port /d' "$sshd_config"
echo "Port $ssh_port" >> "$sshd_config"
if detect_lxc && [[ "$ssh_port" == "22" ]]; then
print_info "LXC détecté - port 22 maintenu"
elif [[ "$ssh_port" == "22022" ]]; then
echo "Port 22" >> "$sshd_config"
print_info "Port 22 ajouté temporairement"
fi
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"
sed -i '/^Ciphers /d' "$sshd_config"
sed -i '/^MACs /d' "$sshd_config"
sed -i '/^KexAlgorithms /d' "$sshd_config"
cat >> "$sshd_config" << 'EOF'
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
update_ssh_param "Banner" "/etc/issue.net" "$sshd_config"
print_info "Test de la configuration SSH..."
if sshd -t 2>&1 | tee -a "$LOG_FILE"; then
print_success "Configuration SSH valide"
print_info "Redémarrage du service SSH..."
if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then
print_success "SSH durci avec succès"
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"
}
configure_banners() {
local step_name="configure_banners"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des bannières légales"
local banner_text="╔════════════════════════════════════════════════════════════╗
║ SYSTÈME SÉCURISÉ ║
║ ║
║ Accès réservé aux personnes autorisées uniquement. ║
║ Toute tentative d'accès non autorisée est interdite ║
║ et sera tracée et poursuivie selon la loi. ║
║ ║
║ Les activités sont surveillées et enregistrées. ║
╚════════════════════════════════════════════════════════════╝"
echo "$banner_text" | tee /etc/issue /etc/issue.net /etc/motd > /dev/null
print_success "Bannières légales configurées"
mark_step_done "$step_name"
}
configure_firewall_ports() {
local step_name="configure_firewall_ports"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des règles de pare-feu basées sur les ports détectés"
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"
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é"
fi
print_success "Configuration pare-feu ignorée"
mark_step_done "$step_name"
return 0
fi
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
local ssh_port=$(get_ssh_port_to_use)
print_info "Autorisation du port SSH $ssh_port..."
ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé' || print_warning "Erreur ajout règle SSH:${ssh_port}"
if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then
print_info "Autorisation temporaire du port SSH 22..."
ufw allow 22/tcp comment 'SSH temporaire (à désactiver)' || print_warning "Erreur ajout règle SSH:22"
fi
declare -A service_ports=(
["HTTP"]="80"
["HTTPS"]="443"
["DNS"]="53"
["DNS-TCP"]="53/tcp"
["DNS-UDP"]="53/udp"
["NTP"]="123"
["NTP-UDP"]="123/udp"
["SMTP"]="25"
["SMTP-Submission"]="587"
["SMTPS"]="465"
["IMAP"]="143"
["IMAPS"]="993"
["POP3"]="110"
["POP3S"]="995"
["FTP"]="21"
["FTP-Data"]="20"
["FTP-SSL"]="990"
["SFTP"]="22"
["MySQL"]="3306"
["PostgreSQL"]="5432"
["MongoDB"]="27017"
["Redis"]="6379"
["Redis-Sentinel"]="26379"
["Elasticsearch"]="9200"
["Elasticsearch-Cluster"]="9300"
["Graylog-Web"]="9000"
["Graylog-API"]="12900"
["Graylog-Syslog"]="514"
["Graylog-Syslog-UDP"]="514/udp"
["Wazuh-Manager"]="1514"
["Wazuh-Manager-UDP"]="1514/udp"
["Wazuh-API"]="55000"
["Prometheus"]="9090"
["Grafana"]="3000"
["Node-Exporter"]="9100"
["Zabbix-Server"]="10051"
["Zabbix-Agent"]="10050"
["Docker-Registry"]="5000"
["Docker-API"]="2375"
["Docker-API-TLS"]="2376"
["Kubernetes-API"]="6443"
["Kubelet-API"]="10250"
["Kube-Proxy"]="10256"
["LDAP"]="389"
["LDAPS"]="636"
["LDAP-Global-Catalog"]="3268"
["LDAPS-Global-Catalog"]="3269"
["Kerberos"]="88"
["Kerberos-UDP"]="88/udp"
["SMB"]="445"
["SMB-UDP"]="445/udp"
["NetBIOS"]="139"
["RDP"]="3389"
["VNC"]="5900"
["VNC-Web"]="5800"
["Matrix-Synapse"]="8008"
["Matrix-Synapse-TLS"]="8448"
["Rocket.Chat"]="3000"
["Mattermost"]="8065"
["Zulip"]="9991"
["Ansible"]="22"
["Salt-Master"]="4505 4506"
["Puppet"]="8140"
["Chef-Server"]="9463"
["NFS"]="2049"
["NFS-UDP"]="2049/udp"
["NFS-Mount"]="20048"
["Samba"]="137 138 139 445"
["Samba-UDP"]="137/udp 138/udp"
["GlusterFS"]="24007 24008 49152-49251"
["Ceph-Mon"]="6789"
["Ceph-OSD"]="6800-7300"
["Bacula-Director"]="9101"
["Bacula-Storage"]="9103"
["Bacula-Client"]="9102"
["Proxmox-Web"]="8006"
["Proxmox-VNC"]="5900-5999"
["Proxmox-SPN"]="3128"
["VMware-ESXi"]="902"
["VirtualBox-RDP"]="3389"
["Gitea"]="3000"
["Gitea-SSH"]="2222"
["Bitwarden"]="80 443"
["Bitwarden-Admin"]="8000"
["Ollama-API"]="11434"
["Teleport-Auth"]="3025"
["Teleport-Proxy"]="3023 3024"
["Teleport-SSH"]="3022"
["Teleport-Kube"]="3026"
["NetBox"]="8000"
["NetBox-SSL"]="8443"
["Nextcloud"]="80 443"
["Jitsi-Meet"]="80 443 10000/udp"
["Jitsi-Videobridge"]="4443"
["Jellyfin"]="8096"
["Jellyfin-SSL"]="8920"
["Plex"]="32400"
["Emby"]="8096"
["Emby-SSL"]="8920"
["GitLab"]="80 443 22"
["Jenkins"]="8080"
["Jenkins-Slave"]="50000"
["SonarQube"]="9000"
["Nexus"]="8081"
["Harbor"]="80 443"
["Harbor-API"]="8280"
["TheHive"]="9000"
["Cortex"]="9001"
["MISP"]="80 443"
["OpenCTI"]="8080"
["Velociraptor"]="8000 8889"
["Suricata"]=""
["Asterisk"]="5060 5061"
["Asterisk-UDP"]="5060/udp"
["FreePBX"]="80 443"
["FreeSWITCH"]="5060 5080 8021"
["RabbitMQ"]="5672 5671 15672"
["RabbitMQ-Management"]="15672"
["Mosquitto"]="1883 8883"
["Mosquitto-Websocket"]="8083 8084"
["Memcached"]="11211"
["Memcached-UDP"]="11211/udp"
["Beanstalkd"]="11300"
["Kong"]="8000 8001 8443 8444"
["Tyk"]="8080"
["Ethereum"]="30303"
["Ethereum-UDP"]="30303/udp"
["Bitcoin"]="8333"
["Bitcoin-Testnet"]="18333"
["Minecraft"]="25565"
["Minecraft-Bedrock"]="19132/udp"
["Steam"]="27015 27016 27017 27018 27019 27020"
["Steam-UDP"]="27015/udp 27016/udp 27017/udp 27018/udp 27019/udp 27020/udp"
["Home Assistant"]="8123"
["Home Assistant-SSL"]="443"
["MQTT-Home"]="1883"
["Zigbee2MQTT"]="8080"
["Active Directory"]="53 88 135 137 138 139 389 445 464 636 3268 3269"
["AD-UDP"]="53/udp 88/udp 123/udp 137/udp 138/udp"
["Exchange"]="25 80 110 143 443 465 587 993 995"
["SharePoint"]="80 443"
["SQL Server"]="1433"
["RPC"]="135"
["NetBIOS-Name"]="137/udp"
["NetBIOS-Datagram"]="138/udp"
["NetBIOS-Session"]="139"
["Apple-File-Service"]="548"
["Apple-Time-Machine"]="548"
["Bonjour"]="5353/udp"
["Google-Cloud-Print"]="5222"
["Google-Cast"]="8009 8443"
)
print_info "Analyse des ports ouverts pour services connus..."
local services_authorized=0
local services_skipped=0
for service_name in "${!service_ports[@]}"; do
local ports="${service_ports[$service_name]}"
for port_spec in $ports; do
local port_num=$(echo "$port_spec" | sed 's|/.*||')
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)
local port_in_range_open=false
for ((p=start_port; p<=end_port; p++)); do
if is_port_open "$p"; then
port_in_range_open=true
break
fi
done
if $port_in_range_open; then
echo -e "${YELLOW} Plage de ports détectée: $port_spec ($service_name)${NC}"
read -p " Autoriser la plage $port_spec ? (o/N): " -r confirm_range
if [[ "$confirm_range" =~ ^[Oo]$ ]]; then
if [[ "$port_spec" == *"/udp" ]]; then
ufw allow "$start_port:$end_port/udp" comment "$service_name" && {
services_authorized=$((services_authorized + 1))
print_info " ✓ Plage $start_port:$end_port/udp autorisée"
} || print_warning " ✗ Erreur avec la plage $port_spec"
else
ufw allow "$start_port:$end_port/tcp" comment "$service_name" && {
services_authorized=$((services_authorized + 1))
print_info " ✓ Plage $start_port:$end_port/tcp autorisée"
} || print_warning " ✗ Erreur avec la plage $port_spec"
fi
else
print_info " ✗ Plage $port_spec non autorisée"
services_skipped=$((services_skipped + 1))
fi
fi
else
if is_port_open "$port_num"; then
print_info " Autorisation du port $port_spec ($service_name)..."
if [[ "$port_spec" == *"/udp" ]]; then
ufw allow "$port_num/udp" comment "$service_name" && {
services_authorized=$((services_authorized + 1))
print_info " ✓ Port $port_num/udp autorisé"
} || print_warning " ✗ Erreur avec le port $port_num/udp"
else
ufw allow "$port_num/tcp" comment "$service_name" && {
services_authorized=$((services_authorized + 1))
print_info " ✓ Port $port_num/tcp autorisé"
} || print_warning " ✗ Erreur avec le port $port_num/tcp"
fi
fi
fi
done
done
print_info "Recherche de ports ouverts non reconnus..."
local unknown_ports_added=0
while read -r port; do
[[ "$port" == "22" || "$port" == "22022" || "$port" == "$ssh_port" ]] && continue
local port_covered=false
for service_name in "${!service_ports[@]}"; do
local ports="${service_ports[$service_name]}"
for port_spec in $ports; do
local port_num=$(echo "$port_spec" | sed 's|/.*||')
if [[ "$port_num" == "$port" ]]; then
port_covered=true
break 2
fi
done
done
if ! $port_covered; then
local service_info=$(grep -E "^[^#].*[[:space:]]$port/" /etc/services 2>/dev/null | head -1)
local service_desc=""
if [[ -n "$service_info" ]]; then
service_desc=$(echo "$service_info" | awk '{print $1}')
fi
if [[ -n "$service_desc" ]]; then
echo -e "${YELLOW} Port $port détecté ($service_desc)${NC}"
else
echo -e "${YELLOW} Port non standard détecté: $port${NC}"
fi
read -p " Autoriser ce port dans le pare-feu ? (o/N): " -r confirm_port
if [[ "$confirm_port" =~ ^[Oo]$ ]]; then
ufw allow "${port}/tcp" comment "Port $port ${service_desc:+- $service_desc}" && {
unknown_ports_added=$((unknown_ports_added + 1))
print_info " ✓ Port $port/tcp autorisé"
} || print_warning " ✗ Erreur avec le port $port/tcp"
echo -n " Autoriser aussi en UDP ? (o/N): "
read -r confirm_udp
if [[ "$confirm_udp" =~ ^[Oo]$ ]]; then
ufw allow "${port}/udp" comment "Port $port/udp ${service_desc:+- $service_desc}" && {
unknown_ports_added=$((unknown_ports_added + 1))
print_info " ✓ Port $port/udp autorisé"
} || print_warning " ✗ Erreur avec le port $port/udp"
fi
else
print_info " ✗ Port $port non autorisé"
fi
fi
done < "$OPEN_PORTS_FILE"
print_info "Configuration limitation ICMP..."
if [[ -f /etc/ufw/before.rules ]]; then
backup_file "/etc/ufw/before.rules"
if ! grep -q "Limiter pings" /etc/ufw/before.rules; then
cat >> /etc/ufw/before.rules << 'EOF'
-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
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é"
print_info "Services autorisés:"
print_info " • Services reconnus: $services_authorized"
print_info " • Services ignorés: $services_skipped"
print_info " • Ports personnalisés: $unknown_ports_added"
echo ""
print_info "Statut UFW:"
ufw status numbered | head -30
else
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
cat > "$BACKUP_DIR/detected_services.log" << EOF
=== SERVICES DÉTECTÉS ET CONFIGURÉS ===
Date: $(date)
Hostname: $(hostname)
Port SSH utilisé: $ssh_port
Ports ouverts détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE")
Services configurés dans UFW:
$(ufw status | grep -A1000 '^To' | grep -v '^To' | grep -v '^\s*$')
=== FIN DU RAPPORT ===
EOF
mark_step_done "$step_name"
}
configure_fail2ban() {
local step_name="configure_fail2ban"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de Fail2ban"
backup_file "/etc/fail2ban/jail.conf"
local ssh_port=$(get_ssh_port_to_use)
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
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 = %(action_)s
EOF
print_info "Fail2ban configuré en mode conteneur"
else
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"
fi
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é"
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"
fi
mark_step_done "$step_name"
}
remove_unneeded_packages() {
local step_name="remove_unneeded_packages"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Suppression des paquets inutiles"
local packages_to_remove=(telnet rsh-client rsh-server netcat-openbsd netcat-traditional nis talk talkd)
for package in "${packages_to_remove[@]}"; do
dpkg -l | grep -q "^ii.*${package}" && {
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "$package" 2>/dev/null
print_info "Paquet $package supprimé"
}
done
DEBIAN_FRONTEND=noninteractive apt-get autoremove -y -qq
print_success "Paquets inutiles supprimés"
mark_step_done "$step_name"
}
restrict_file_permissions() {
local step_name="restrict_file_permissions"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Restriction des permissions des fichiers critiques"
chmod 644 /etc/passwd 2>/dev/null || true
chmod 600 /etc/shadow 2>/dev/null || true
chmod 644 /etc/group 2>/dev/null || true
chmod 600 /etc/gshadow 2>/dev/null || true
chmod 600 /etc/sudoers 2>/dev/null || true
chmod 750 /etc/sudoers.d 2>/dev/null || true
chmod 600 /boot/grub/grub.cfg 2>/dev/null || true
chmod 644 /etc/ssh/ssh_config 2>/dev/null || true
chmod 600 /etc/ssh/sshd_config 2>/dev/null || true
print_success "Permissions des fichiers critiques restreintes"
mark_step_done "$step_name"
}
disable_risky_kernel_modules() {
local step_name="disable_risky_kernel_modules"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Désactivation des modules noyau risqués"
cat > /etc/modprobe.d/hardening-blacklist.conf << 'EOF'
blacklist dccp
install dccp /bin/true
blacklist sctp
install sctp /bin/true
blacklist rds
install rds /bin/true
blacklist tipc
install tipc /bin/true
blacklist cramfs
install cramfs /bin/true
blacklist freevxfs
install freevxfs /bin/true
blacklist jffs2
install jffs2 /bin/true
blacklist hfs
install hfs /bin/true
blacklist hfsplus
install hfsplus /bin/true
blacklist squashfs
install squashfs /bin/true
blacklist udf
install udf /bin/true
blacklist firewire-core
install firewire-core /bin/true
blacklist thunderbolt
install thunderbolt /bin/true
EOF
detect_container && \
print_warning "Conteneur - modules gérés par l'hôte" || \
print_info "Modules blacklistés"
print_success "Modules noyau risqués désactivés"
mark_step_done "$step_name"
}
configure_security_limits() {
local step_name="configure_security_limits"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des limites de sécurité"
backup_file "/etc/security/limits.conf"
cat >> /etc/security/limits.conf << 'EOF'
* hard core 0
* soft nproc 512
* hard nproc 1024
* soft nofile 65536
* hard nofile 65536
EOF
print_success "Limites de sécurité configurées"
mark_step_done "$step_name"
}
verify_packages_integrity() {
local step_name="verify_packages_integrity"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Vérification de l'intégrité des paquets"
print_info "Vérification avec debsums..."
debsums -s 2>&1 | tee -a "$LOG_FILE" || print_warning "Certains paquets ont des erreurs"
print_success "Vérification d'intégrité terminée"
mark_step_done "$step_name"
}
configure_automatic_updates() {
local step_name="configure_automatic_updates"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des mises à jour automatiques"
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"
}
configure_aide_cron() {
local step_name="configure_aide_cron"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
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"
}
harden_smtp_banner() {
local step_name="harden_smtp_banner"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement bannière SMTP"
if dpkg -l | grep -q "^ii.*postfix"; then
backup_file "/etc/postfix/main.cf"
postconf -e "smtpd_banner = \$myhostname ESMTP"
postconf -e "disable_vrfy_command = yes"
systemctl reload postfix 2>/dev/null || true
print_success "Bannière Postfix durcie"
elif dpkg -l | grep -q "^ii.*exim4"; then
backup_file "/etc/exim4/exim4.conf.template"
sed -i 's/^smtp_banner.*/smtp_banner = "${primary_hostname} ESMTP"/' \
/etc/exim4/exim4.conf.template 2>/dev/null || true
update-exim4.conf 2>/dev/null || true
print_success "Bannière Exim4 durcie"
else
print_info "Aucun serveur SMTP détecté"
fi
mark_step_done "$step_name"
}
harden_systemd_services() {
local step_name="harden_systemd_services"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement services systemd"
command -v systemd-analyze > /dev/null || {
print_warning "systemd-analyze non disponible"
mark_step_done "$step_name"
return 0
}
if detect_container; then
print_warning "Conteneur détecté - durcissement systemd limité"
local services=(ssh sshd fail2ban chrony)
for service in "${services[@]}"; do
local unit="${service}.service"
local override_dir="/etc/systemd/system/${unit}.d"
if [[ -d "$override_dir" ]]; then
print_info "Nettoyage des overrides systemd pour $unit"
rm -rf "$override_dir"
fi
done
systemctl daemon-reload
print_success "Configuration systemd nettoyée"
mark_step_done "$step_name"
return 0
fi
local services=(ssh sshd fail2ban chrony)
local hardened=0
for service in "${services[@]}"; do
local unit="${service}.service"
systemctl list-unit-files | grep -q "^${unit}" || continue
if ! systemctl is-enabled "$unit" > /dev/null 2>&1 && \
! systemctl is-active "$unit" > /dev/null 2>&1; then
print_info "Service $unit non actif - ignoré"
continue
fi
mkdir -p "/etc/systemd/system/${unit}.d"
if [[ "$service" == "ssh" || "$service" == "sshd" ]]; then
cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=read-only
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictRealtime=yes
EOF
else
cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log /var/lib/${service} /run/${service}
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
EOF
fi
hardened=$((hardened + 1))
print_info "Service $unit durci"
done
systemctl daemon-reload
print_info "Vérification des services critiques..."
for service in ssh sshd; do
local unit="${service}.service"
if systemctl list-unit-files | grep -q "^${unit}"; then
if systemctl is-active --quiet "$unit" 2>/dev/null; then
systemctl restart "$unit" 2>&1 | tee -a "$LOG_FILE" || {
print_error "Échec redémarrage $unit - restauration configuration"
rm -rf "/etc/systemd/system/${unit}.d"
systemctl daemon-reload
systemctl restart "$unit"
}
fi
fi
done
print_success "$hardened service(s) systemd durci(s)"
mark_step_done "$step_name"
}
configure_advanced_pam() {
local step_name="configure_advanced_pam"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration PAM avancée"
backup_file "/etc/pam.d/common-password"
grep -q "pam_unix.so" /etc/pam.d/common-password && {
grep -q "sha512" /etc/pam.d/common-password || \
sed -i 's/pam_unix.so.*/& sha512/' /etc/pam.d/common-password
grep -q "rounds=" /etc/pam.d/common-password || \
sed -i 's/pam_unix.so.*/& rounds=500000/' /etc/pam.d/common-password
}
print_info "Application expiration mots de passe aux utilisateurs..."
while IFS=: read -r user _ uid _ _ _ shell; do
[[ "$uid" -ge 1000 || "$user" == "root" ]] && \
[[ -n "$shell" ]] && \
[[ "$shell" != *"nologin"* ]] && \
[[ "$shell" != *"false"* ]] && {
chage -M 90 "$user" 2>/dev/null || true
chage -d 0 "$user" 2>/dev/null || true
print_info "$user: Expiration 90 jours configurée"
}
done < /etc/passwd
print_success "Configuration PAM avancée appliquée"
mark_step_done "$step_name"
}
check_partition_layout() {
local step_name="check_partition_layout"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Vérification disposition partitions"
local partitions=(/home /tmp /var /var/log /var/log/audit)
local warnings=()
for partition in "${partitions[@]}"; do
mount | grep -q " on ${partition} " && {
local device=$(mount | grep " on ${partition} " | awk '{print $1}')
print_info "$partition: Partition séparée ($device)"
} || {
warnings+=("$partition")
print_warning "$partition: Non monté séparément"
}
done
[[ ${#warnings[@]} -eq 0 ]] && \
print_success "Toutes les partitions critiques séparées" || {
print_warning "${#warnings[@]} partition(s) non séparée(s)"
detect_container && \
print_info "Conteneur: partitions gérées par l'hôte"
}
mark_step_done "$step_name"
}
check_vmlinuz() {
local step_name="check_vmlinuz"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Vérification fichiers noyau"
local files=(/vmlinuz /boot/vmlinuz "/boot/vmlinuz-$(uname -r)" "/boot/initrd.img-$(uname -r)")
local found=0
for kfile in "${files[@]}"; do
[[ -f "$kfile" ]] && {
found=$((found + 1))
print_info "$kfile ($(du -h "$kfile" | cut -f1))"
}
done
[[ $found -eq 0 ]] && {
print_warning "Aucun fichier noyau trouvé"
detect_container && \
print_info "Conteneur: noyau géré par l'hôte" || {
print_error "Système physique: fichiers noyau manquants!"
apt-get install --reinstall "linux-image-$(uname -r)" 2>/dev/null || \
print_warning "Échec réinstallation noyau"
}
} || print_success "$found fichier(s) noyau trouvé(s)"
mark_step_done "$step_name"
}
run_chkrootkit() {
local step_name="run_chkrootkit"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Exécution de chkrootkit"
print_info "Scan rootkit en cours..."
chkrootkit > "$BACKUP_DIR/chkrootkit_report.log" 2>&1 || \
print_warning "Chkrootkit a détecté des avertissements"
print_success "Scan chkrootkit terminé"
mark_step_done "$step_name"
}
prepare_ssh_cleanup() {
local step_name="prepare_ssh_cleanup"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Préparation nettoyage port SSH 22"
local ssh_port=$(get_ssh_port_to_use)
if [[ "$ssh_port" == "22022" ]]; then
print_warning "Port SSH 22 toujours actif (temporaire)"
print_info "Après avoir testé SSH sur le port $ssh_port:"
print_info "Exécutez: $0 --cleanup-ssh"
else
print_info "Conteneur LXC - Port SSH principal: 22"
print_info "Aucun nettoyage nécessaire"
fi
mark_step_done "$step_name"
}
run_lynis_audit() {
local step_name="run_lynis_audit"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Exécution de l'audit Lynis"
print_info "Audit système complet en cours..."
lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1
local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1)
local max_score=100
[[ -z "$score" ]] && score=0
echo "$score" > "/tmp/lynis_score.txt"
echo "$max_score" > "/tmp/lynis_max_score.txt"
local percentage=$((score * 100 / max_score))
local bar_length=50
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+="]"
local color=$RED
[[ $score -ge 80 ]] && color=$GREEN
[[ $score -ge 60 && $score -lt 80 ]] && color=$YELLOW
print_success "Audit Lynis terminé"
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"
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
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
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 ""
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
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É v8.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}"
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
if [[ "$lynis_score" != "N/A" ]]; then
local percentage=$((lynis_score * 100 / lynis_max_score))
local bar_length=25
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+="]"
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"
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
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 ""
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 ""
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 ""
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
rm -f /tmp/lynis_score.txt /tmp/lynis_max_score.txt 2>/dev/null || true
}
main() {
parse_arguments "$@"
if $LIST_STEPS; then
list_all_steps
exit 0
fi
if $SHOW_STATUS; then
show_step_status
exit 0
fi
if [[ $# -eq 1 && "$1" == "--cleanup-ssh" ]]; then
cleanup_ssh_port
return 0
fi
check_requirements
if [[ ${#FORCE_STEPS[@]} -gt 0 ]]; then
echo -e "${YELLOW}Étapes forcées:${NC} ${FORCE_STEPS[*]}"
fi
if [[ ${#SKIP_STEPS[@]} -gt 0 ]]; then
echo -e "${YELLOW}Étapes ignorées:${NC} ${SKIP_STEPS[*]}"
fi
if $FORCE_ALL; then
echo -e "${RED}MODE FORCE ALL ACTIVÉ - Toutes les étapes seront ré-exécutées${NC}"
echo ""
read -p "Appuyez sur Entrée pour continuer ou Ctrl+C pour annuler..."
fi
print_header
log_message "==================================================" "START"
log_message "Démarrage durcissement système v8.0" "START"
log_message "Système: $(hostname)" "START"
log_message "Type: $(detect_container && echo "Conteneur" || echo "Physique/VM")" "START"
log_message "Mode: $($FORCE_ALL && echo "FORCE ALL" || echo "Normal")" "START"
log_message "Étapes forcées: ${FORCE_STEPS[*]}" "START"
log_message "Étapes ignorées: ${SKIP_STEPS[*]}" "START"
log_message "==================================================" "START"
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
print_summary
log_message "==================================================" "END"
log_message "Durcissement terminé - Durée: $((SECONDS / 60)) min" "END"
log_message "Mode: $($FORCE_ALL && echo "FORCE ALL" || echo "Normal")" "END"
log_message "Ports détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE" 2>/dev/null || echo "Aucun")" "END"
log_message "==================================================" "END"
}
# ==============================================================================
# EXÉCUTION PRINCIPALE
# ==============================================================================
trap 'print_error "Script interrompu"; exit 130' INT TERM
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"