system_hardering/system_hardening_optimized.sh

3004 lines
110 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.2
# Date: $(date +%Y-%m-%d)
# Author: Security Team
# Description: Système de durcissement sécurité pour Debian/Ubuntu LTS
# Mode autonome avec valeurs par défaut sécurisées
# License: GPLv3
################################################################################
set -euo pipefail
# ==============================================================================
# CONFIGURATION AUTONOME
# ==============================================================================
readonly LOG_FILE="/var/log/system_hardening.log"
readonly STATUS_FILE="/var/log/hardening_status.log"
readonly BACKUP_DIR="/root/backup_hardening_$(date +%Y%m%d_%H%M%S)"
readonly SECURITY_REPORT="/var/log/security_report_$(date +%Y%m%d).log"
readonly OPEN_PORTS_FILE="/tmp/open_ports_detected.txt"
# Valeurs par défaut pour mode autonome
readonly DEFAULT_SSH_PORT=22022
readonly DEFAULT_TIMEZONE="Europe/Paris"
readonly DEFAULT_PASS_MAX_DAYS=90
readonly DEFAULT_PASS_MIN_DAYS=7
readonly DEFAULT_UMASK="027"
# Configuration autonome (modifiable via variables d'environnement)
: "${AUTO_SSH_PORT:=$DEFAULT_SSH_PORT}"
: "${AUTO_TIMEZONE:=$DEFAULT_TIMEZONE}"
: "${AUTO_PASS_MAX_DAYS:=$DEFAULT_PASS_MAX_DAYS}"
: "${AUTO_PASS_MIN_DAYS:=$DEFAULT_PASS_MIN_DAYS}"
: "${AUTO_UMASK:=$DEFAULT_UMASK}"
: "${AUTO_DISABLE_ROOT_LOGIN:=no}"
: "${AUTO_ENABLE_FAIL2BAN:=yes}"
: "${AUTO_ENABLE_UFW:=yes}"
: "${AUTO_ENABLE_AIDE:=yes}"
: "${AUTO_ENABLE_CLAMAV:=yes}"
: "${AUTO_SKIP_PORTS_DETECTION:=no}"
: "${AUTO_SKIP_LYNIS:=no}"
: "${AUTO_YES:=no}"
: "${AUTO_CLEANUP_SSH:=no}"
: "${AUTO_CHANGE_ROOT_PWD:=yes}"
: "${AUTO_SKIP_DEBSUMS_CONTAINER:=yes}"
TOTAL_STEPS=33
CURRENT_STEP=1
# Variables de contrôle
FORCE_ALL=false
FORCE_STEPS=()
SKIP_STEPS=()
RESET_ALL=false
LIST_STEPS=false
SHOW_STATUS=false
UNATTENDED=false
# ==============================================================================
# COULEURS
# ==============================================================================
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly CYAN='\033[0;36m'
readonly BLUE='\033[0;34m'
readonly MAGENTA='\033[0;35m'
readonly NC='\033[0m'
# ==============================================================================
# DÉFINITION DES ÉTAPES
# ==============================================================================
declare -A STEP_DESCRIPTIONS=(
["install_security_tools"]="Installation des outils de sécurité"
["change_root_password"]="Changement mot de passe root aléatoire"
["detect_open_ports"]="Détection des ports ouverts"
["configure_process_accounting"]="Configuration Process Accounting"
["configure_sysctl_security"]="Durcissement sysctl"
["configure_log_permissions"]="Permissions des logs"
["configure_pam_password_policy"]="Politique mots de passe PAM"
["configure_login_defs"]="Configuration login.defs"
["configure_umask"]="Configuration umask"
["configure_aide_sha512"]="Configuration AIDE SHA512"
["initialize_aide_db"]="Initialisation base AIDE"
["configure_clamav"]="Configuration ClamAV"
["configure_chrony"]="Configuration Chrony"
["harden_ssh"]="Durcissement SSH"
["configure_banners"]="Configuration bannières"
["configure_firewall_ports"]="Configuration pare-feu UFW"
["configure_fail2ban"]="Configuration Fail2ban"
["remove_unneeded_packages"]="Suppression paquets inutiles"
["restrict_file_permissions"]="Restriction permissions fichiers"
["disable_risky_kernel_modules"]="Désactivation modules noyau"
["configure_security_limits"]="Configuration limites sécurité"
["verify_packages_integrity"]="Vérification intégrité paquets"
["configure_automatic_updates"]="Configuration mises à jour auto"
["configure_aide_cron"]="Configuration tâche AIDE cron"
["harden_smtp_banner"]="Durcissement bannière SMTP"
["harden_systemd_services"]="Durcissement services systemd"
["configure_advanced_pam"]="Configuration PAM avancée"
["check_partition_layout"]="Vérification partitions"
["check_vmlinuz"]="Vérification fichiers noyau"
["run_chkrootkit"]="Exécution chkrootkit"
["prepare_ssh_cleanup"]="Préparation nettoyage SSH"
["run_lynis_audit"]="Audit Lynis final"
)
# ==============================================================================
# FONCTIONS UTILITAIRES
# ==============================================================================
log_message() {
local message="$1"
local level="${2:-INFO}"
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
}
print_step() {
local step_title="$1"
echo -e "\n${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${YELLOW} ÉTAPE ${CURRENT_STEP}/${TOTAL_STEPS}: $step_title${NC}"
echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}"
log_message "Début étape $CURRENT_STEP: $step_title" "STEP"
CURRENT_STEP=$((CURRENT_STEP + 1))
}
print_success() {
echo -e "${GREEN}${NC} $1"
log_message "$1" "SUCCESS"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
log_message "$1" "WARNING"
}
print_error() {
echo -e "${RED}${NC} $1" >&2
log_message "$1" "ERROR"
}
print_info() {
echo -e "${BLUE}${NC} $1"
log_message "$1" "INFO"
}
auto_confirm() {
[[ "$AUTO_YES" == "yes" ]] && return 0
[[ "$UNATTENDED" == true ]] && return 0
return 1
}
# ==============================================================================
# GESTION DES ÉTAPES
# ==============================================================================
check_step_done() {
local step_name="$1"
if $FORCE_ALL; then
return 1
fi
if [[ " ${FORCE_STEPS[@]} " =~ " ${step_name} " ]]; then
print_info "Forçage de l'étape: $step_name"
return 1
fi
if [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then
print_info "Saut de l'étape: $step_name"
return 0
fi
grep -q "^${step_name}$" "$STATUS_FILE" 2>/dev/null
}
mark_step_done() {
local step_name="$1"
if [[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]]; then
print_info "Étape $step_name ignorée - non marquée comme terminée"
return 0
fi
sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true
echo "$step_name" >> "$STATUS_FILE"
log_message "Étape '$step_name' marquée comme terminée" "STATUS"
}
skip_step() {
local step_name="$1"
print_info "Étape déjà effectuée : $step_name"
CURRENT_STEP=$((CURRENT_STEP + 1))
}
detect_container() {
# -----------------------------------------------------------------
# DÉTECTION DE TOUS TYPES DE CONTENEURS (LXC, Docker, systemd-nspawn, etc.)
# Retourne 0 si dans un conteneur, 1 sinon
# -----------------------------------------------------------------
# 1. FICHIERS SPÉCIFIQUES
[[ -f /.dockerenv ]] && { log_message "Conteneur détecté: /.dockerenv" "DETECT"; return 0; }
[[ -d /dev/lxc ]] && { log_message "Conteneur détecté: /dev/lxc" "DETECT"; return 0; }
[[ -f /.lxcpid ]] && { log_message "Conteneur détecté: /.lxcpid" "DETECT"; return 0; }
# 2. VARIABLES D'ENVIRONNEMENT (utilisation de ${var:-} pour éviter "unbound variable")
[[ -n "${container:-}" ]] && { log_message "Conteneur détecté: variable \$container=$container" "DETECT"; return 0; }
[[ -n "${DOCKER_HOST:-}" ]] && { log_message "Conteneur détecté: variable \$DOCKER_HOST" "DETECT"; return 0; }
[[ -n "${KUBERNETES_SERVICE_HOST:-}" ]] && { log_message "Conteneur détecté: Kubernetes" "DETECT"; return 0; }
# 3. CGROUPS (compatible v1 et v2)
if [[ -f /proc/1/cgroup ]]; then
# Lecture de /proc/1/cgroup (plus fiable que /proc/self/cgroup)
local cgroup_content
cgroup_content=$(cat /proc/1/cgroup 2>/dev/null || echo "")
# Modèles de conteneurs dans cgroups
if echo "$cgroup_content" | grep -qE "(docker|lxc|kubepods|containerd|podman|buildkit|systemd-nspawn|libpod)"; then
log_message "Conteneur détecté: cgroups pattern" "DETECT"
return 0
fi
# Cgroups v2: format "0::/"
if echo "$cgroup_content" | grep -q "^0::/"; then
# Vérifier si le chemin n'est pas un chemin système standard
local cgroup_path
cgroup_path=$(echo "$cgroup_content" | grep "^0::/" | cut -d: -f3)
# Les chemins typiques des conteneurs
if [[ "$cgroup_path" == "/" ]] ||
[[ "$cgroup_path" == /user.slice* ]] ||
[[ "$cgroup_path" == /system.slice* ]]; then
# Pour cgroups v2, vérifier l'isolation des namespaces
if [[ -r /proc/1/ns/pid ]] && [[ -r /proc/self/ns/pid ]]; then
local pid1_inode
local self_inode
pid1_inode=$(stat -Lc '%i' /proc/1/ns/pid 2>/dev/null)
self_inode=$(stat -Lc '%i' /proc/self/ns/pid 2>/dev/null)
# Si les inodes sont différents, on est dans un namespace PID isolé (conteneur)
if [[ -n "$pid1_inode" ]] && [[ -n "$self_inode" ]] && [[ "$pid1_inode" != "$self_inode" ]]; then
log_message "Conteneur détecté: namespace PID isolé (cgroups v2)" "DETECT"
return 0
fi
fi
else
# Chemin non standard -> probablement un conteneur
log_message "Conteneur détecté: cgroups v2 path non standard: $cgroup_path" "DETECT"
return 0
fi
fi
fi
# 4. SYSTEMD-DETECT-VIRT (outil natif)
if command -v systemd-detect-virt >/dev/null 2>&1; then
local virt
virt=$(systemd-detect-virt 2>/dev/null)
case "$virt" in
"container"|"lxc"|"lxc-libvirt"|"systemd-nspawn"|"docker"|"podman"|"wsl")
log_message "Conteneur détecté: systemd-detect-virt=$virt" "DETECT"
return 0
;;
esac
fi
# 5. VÉRIFICATION DES PROCESSUS ET NAMESPACES
# Vérifier l'isolation des namespaces (méthode avancée)
if [[ -r /proc/1/ns/pid ]] && [[ -r /proc/self/ns/pid ]]; then
local ns_pid1
local ns_self
ns_pid1=$(readlink /proc/1/ns/pid 2>/dev/null)
ns_self=$(readlink /proc/self/ns/pid 2>/dev/null)
if [[ -n "$ns_pid1" ]] && [[ -n "$ns_self" ]] && [[ "$ns_pid1" != "$ns_self" ]]; then
log_message "Conteneur détecté: namespace PID différent (PID1: $ns_pid1, Self: $ns_self)" "DETECT"
return 0
fi
fi
# 6. VÉRIFICATION DES PROCESSUS DE GESTION DE CONTENEURS
if ps -eo comm 2>/dev/null | grep -qE "^(containerd|dockerd|runc|crun|podman|CRIO)$"; then
# Si un gestionnaire de conteneurs tourne, vérifier si nous sommes dans un conteneur
if [[ "$(unshare --pid --fork --mount-proc readlink /proc/1/exe 2>/dev/null)" != "/sbin/init" ]]; then
log_message "Conteneur détecté: gestionnaire de conteneurs actif" "DETECT"
return 0
fi
fi
# 7. VÉRIFICATION DE L'INIT (PID 1)
local init_cmd
init_cmd=$(ps -p 1 -o comm= 2>/dev/null || cat /proc/1/comm 2>/dev/null)
case "$init_cmd" in
"systemd"|"init"|"upstart")
# Init système normal - probablement pas un conteneur
;;
"containerd-shim"|"runc"|"docker-init"|"tini")
log_message "Conteneur détecté: init spécial: $init_cmd" "DETECT"
return 0
;;
esac
# 8. FICHIERS SPÉCIFIQUES À CERTAINS CONTENEURS
[[ -f /run/.containerenv ]] && { log_message "Conteneur détecté: /run/.containerenv" "DETECT"; return 0; }
[[ -d /run/host ]] && {
# /run/host existe souvent dans les conteneurs systemd-nspawn
if ! mountpoint -q /run/host 2>/dev/null; then
log_message "Conteneur détecté: /run/host présent" "DETECT"
return 0
fi
}
# 9. VOLUMES DOCKER SPÉCIFIQUES (optionnel)
if [[ -d /var/lib/docker ]] && mountpoint -q /var/lib/docker 2>/dev/null; then
# /var/lib/docker est monté - pourrait être un daemon Docker
log_message "Docker détecté via /var/lib/docker monté" "DETECT"
# Note: Ceci détecte le daemon Docker, pas forcément qu'on est dans un conteneur
fi
# Aucune détection positive
log_message "Aucun conteneur détecté" "DETECT"
return 1
}
detect_lxc() {
# Détection spécifique LXC
[[ -d /dev/lxc ]] && return 0
[[ -f /.lxcpid ]] && return 0
grep -q "lxc" /proc/1/cgroup 2>/dev/null && return 0
[[ "$(systemd-detect-virt 2>/dev/null)" == "lxc" ]] && return 0
return 1
}
backup_file() {
local file_path="$1"
[[ -f "$file_path" ]] && {
mkdir -p "$BACKUP_DIR"
cp "$file_path" "${BACKUP_DIR}/"
log_message "Sauvegarde de $file_path" "BACKUP"
}
}
add_unique_line() {
local line="$1"
local file="$2"
grep -qF "$line" "$file" 2>/dev/null || {
echo "$line" >> "$file"
log_message "Ajout ligne: '$line' -> $file" "CONFIG"
}
}
update_config_value() {
local file="$1"
local key="$2"
local value="$3"
backup_file "$file"
if grep -q "^${key}" "$file"; then
sed -i "s/^${key}.*/${key} ${value}/" "$file"
else
echo "${key} ${value}" >> "$file"
fi
}
# ==============================================================================
# FONCTIONS DE CONTRÔLE
# ==============================================================================
reset_step() {
local step_name="$1"
if [[ -f "$STATUS_FILE" ]]; then
sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true
print_success "Étape '$step_name' réinitialisée"
case $step_name in
"harden_ssh")
if [[ -f "${BACKUP_DIR}/sshd_config" ]]; then
if auto_confirm; then
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || true
print_success "Configuration SSH restaurée"
else
read -p "Restaurer la configuration SSH originale ? (o/N): " -r restore_ssh
if [[ "$restore_ssh" =~ ^[Oo]$ ]]; then
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || true
print_success "Configuration SSH restaurée"
fi
fi
fi
;;
"configure_firewall_ports")
if command -v ufw > /dev/null 2>&1; then
if auto_confirm; then
ufw --force reset > /dev/null 2>&1 || true
print_success "Règles UFW réinitialisées"
else
read -p "Réinitialiser les règles UFW ? (o/N): " -r reset_ufw
if [[ "$reset_ufw" =~ ^[Oo]$ ]]; then
ufw --force reset > /dev/null 2>&1 || true
print_success "Règles UFW réinitialisées"
fi
fi
fi
;;
"configure_fail2ban")
if [[ -f "${BACKUP_DIR}/jail.local" ]]; then
if auto_confirm; then
cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true
systemctl restart fail2ban 2>/dev/null || true
print_success "Configuration Fail2ban restaurée"
else
read -p "Restaurer la configuration Fail2ban originale ? (o/N): " -r restore_fail2ban
if [[ "$restore_fail2ban" =~ ^[Oo]$ ]]; then
cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true
systemctl restart fail2ban 2>/dev/null || true
print_success "Configuration Fail2ban restaurée"
fi
fi
fi
;;
esac
else
print_warning "Fichier de statut non trouvé"
fi
}
reset_all_steps() {
print_step "Réinitialisation de toutes les étapes"
if ! auto_confirm; then
echo -e "${RED}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ⚠ ATTENTION CRITIQUE ⚠ ║${NC}"
echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "Cette action va:"
echo "1. Supprimer l'historique d'exécution de toutes les étapes"
echo "2. Permettre une ré-exécution complète du script"
echo "3. NE supprime PAS les configurations appliquées"
echo ""
echo -n "Confirmez-vous la réinitialisation complète ? (oui/NON): "
read -r confirmation
if [[ "$confirmation" != "oui" ]]; then
print_error "Réinitialisation annulée"
exit 1
fi
else
print_info "Mode auto: réinitialisation confirmée automatiquement"
fi
if [[ -f "$STATUS_FILE" ]]; then
cp "$STATUS_FILE" "${STATUS_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
print_info "Ancien statut sauvegardé"
fi
> "$STATUS_FILE"
print_success "Toutes les étapes ont été réinitialisées"
if auto_confirm; then
print_info "Mode auto: aucune restauration de configuration"
return 0
fi
read -p "Voulez-vous restaurer les configurations originales ? (o/N): " -r restore_all
if [[ "$restore_all" =~ ^[Oo]$ ]]; then
echo ""
echo "Sélectionnez les configurations à restaurer:"
echo "1) SSH"
echo "2) UFW (pare-feu)"
echo "3) Fail2ban"
echo "4) Toutes les configurations"
echo "5) Aucune"
echo ""
read -p "Choix (1-5): " -r restore_choice
case $restore_choice in
1) restore_ssh_config ;;
2) restore_ufw_config ;;
3) restore_fail2ban_config ;;
4) restore_all_configs ;;
*) print_info "Aucune restauration effectuée" ;;
esac
fi
}
restore_ssh_config() {
if [[ -f "${BACKUP_DIR}/sshd_config" ]]; then
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || true
print_success "Configuration SSH restaurée"
else
print_warning "Sauvegarde SSH non trouvée"
fi
}
restore_ufw_config() {
if command -v ufw > /dev/null 2>&1; then
ufw --force reset > /dev/null 2>&1 || true
print_success "Configuration UFW réinitialisée"
fi
}
restore_fail2ban_config() {
if [[ -f "${BACKUP_DIR}/jail.local" ]]; then
cp "${BACKUP_DIR}/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true
systemctl restart fail2ban 2>/dev/null || true
print_success "Configuration Fail2ban restaurée"
fi
}
restore_all_configs() {
restore_ssh_config
restore_ufw_config
restore_fail2ban_config
}
list_all_steps() {
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ LISTE DES ÉTAPES DISPONIBLES ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
local i=1
for step_name in "${!STEP_DESCRIPTIONS[@]}"; do
local status="❌ Non exécutée"
if check_step_done "$step_name" 2>/dev/null; then
status="✅ Terminée"
fi
printf "${BLUE}%2d.${NC} %-35s ${GREEN}%-12s${NC}\n" \
"$i" "${STEP_DESCRIPTIONS[$step_name]}" "$status"
i=$((i + 1))
done
}
show_step_status() {
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ STATUT DES ÉTAPES ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
local completed=0
local total=${#STEP_DESCRIPTIONS[@]}
for step_name in "${!STEP_DESCRIPTIONS[@]}"; do
local description="${STEP_DESCRIPTIONS[$step_name]}"
if check_step_done "$step_name" 2>/dev/null; then
echo -e "${GREEN}${description}${NC}"
completed=$((completed + 1))
else
echo -e "${RED}${description}${NC}"
fi
done
echo ""
echo -e "${YELLOW}RÉSUMÉ:${NC}"
echo -e " Progression: ${completed}/${total} étapes"
}
# ==============================================================================
# PARSING DES ARGUMENTS AMÉLIORÉ
# ==============================================================================
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
--force-all)
FORCE_ALL=true
print_warning "Mode FORCE ALL activé"
shift
;;
--force-step=*)
local step="${1#*=}"
FORCE_STEPS+=("$step")
print_info "Étape forcée: $step"
shift
;;
--skip-step=*)
local step="${1#*=}"
SKIP_STEPS+=("$step")
print_info "Étape ignorée: $step"
shift
;;
--reset-step=*)
local step="${1#*=}"
reset_step "$step"
exit 0
;;
--reset-all)
reset_all_steps
exit 0
;;
--list-steps)
LIST_STEPS=true
shift
;;
--show-status)
SHOW_STATUS=true
shift
;;
--cleanup-ssh)
AUTO_CLEANUP_SSH="yes"
shift
;;
--unattended)
UNATTENDED=true
AUTO_YES="yes"
print_info "Mode autonome activé"
shift
;;
--ssh-port=*)
AUTO_SSH_PORT="${1#*=}"
print_info "Port SSH configuré: $AUTO_SSH_PORT"
shift
;;
--timezone=*)
AUTO_TIMEZONE="${1#*=}"
print_info "Fuseau horaire: $AUTO_TIMEZONE"
shift
;;
--change-root-pwd)
AUTO_CHANGE_ROOT_PWD="yes"
print_info "Changement mot de passe root activé"
shift
;;
--yes|-y)
AUTO_YES="yes"
print_info "Mode auto-confirmation activé"
shift
;;
--help|-h)
show_help
exit 0
;;
*)
print_error "Option inconnue: $1"
show_help
exit 1
;;
esac
done
}
show_help() {
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ AIDE - SYSTEM HARDENING SCRIPT v8.2 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${GREEN}USAGE:${NC}"
echo " $0 [OPTIONS]"
echo ""
echo -e "${GREEN}OPTIONS:${NC}"
echo " --force-all Forcer l'exécution de toutes les étapes"
echo " --force-step=NOM Forcer une étape spécifique"
echo " --skip-step=NOM Sauter une étape spécifique"
echo " --reset-step=NOM Réinitialiser une étape"
echo " --reset-all Réinitialiser toutes les étapes"
echo " --list-steps Lister toutes les étapes disponibles"
echo " --show-status Afficher le statut des étapes"
echo " --cleanup-ssh Nettoyer le port SSH 22 (après test)"
echo " --unattended Mode autonome (pas d'interaction)"
echo " --ssh-port=PORT Définir le port SSH personnalisé"
echo " --timezone=TZ Définir le fuseau horaire"
echo " --change-root-pwd Changer mot de passe root aléatoirement"
echo " --yes, -y Répondre 'oui' à toutes les questions"
echo " --help, -h Afficher cette aide"
echo ""
echo -e "${GREEN}VARIABLES D'ENVIRONNEMENT:${NC}"
echo " AUTO_SSH_PORT Port SSH (défaut: 22022)"
echo " AUTO_TIMEZONE Fuseau horaire (défaut: Europe/Paris)"
echo " AUTO_PASS_MAX_DAYS Max jours pour mot de passe (défaut: 90)"
echo " AUTO_PASS_MIN_DAYS Min jours pour mot de passe (défaut: 7)"
echo " AUTO_UMASK Umask par défaut (défaut: 027)"
echo " AUTO_DISABLE_ROOT_LOGIN Désactiver login root (défaut: no)"
echo " AUTO_ENABLE_FAIL2BAN Activer Fail2ban (défaut: yes)"
echo " AUTO_ENABLE_UFW Activer UFW (défaut: yes)"
echo " AUTO_ENABLE_AIDE Activer AIDE (défaut: yes)"
echo " AUTO_ENABLE_CLAMAV Activer ClamAV (défaut: yes)"
echo " AUTO_SKIP_PORTS_DETECTION Sauter détection ports (défaut: no)"
echo " AUTO_SKIP_LYNIS Sauter audit Lynis (défaut: no)"
echo " AUTO_YES Répondre oui automatiquement (défaut: no)"
echo " AUTO_CLEANUP_SSH Nettoyer SSH automatiquement (défaut: no)"
echo " AUTO_CHANGE_ROOT_PWD Changer mdp root aléatoirement (défaut: no)"
echo ""
echo -e "${GREEN}EXEMPLES:${NC}"
echo " # Mode autonome complet"
echo " $0 --unattended"
echo ""
echo " # Mode autonome avec port SSH personnalisé"
echo " AUTO_SSH_PORT=2222 $0 --unattended"
echo ""
echo " # Mode avec auto-confirmation"
echo " $0 --yes"
echo ""
echo " # Mode autonome sans certains composants"
echo " AUTO_ENABLE_CLAMAV=yes AUTO_ENABLE_AIDE=yes $0 --unattended"
echo ""
echo -e "${RED}⚠ AVERTISSEMENT:${NC}"
echo " En mode autonome, le script prend des décisions automatiques."
echo " Assurez-vous d'avoir testé les configurations avant en production."
}
# ==============================================================================
# FONCTIONS DE DÉTECTION DES PORTS (AMÉLIORÉES)
# ==============================================================================
detect_open_ports() {
local step_name="detect_open_ports"
if [[ "$AUTO_SKIP_PORTS_DETECTION" == "yes" ]]; then
print_info "Détection des ports ignorée (AUTO_SKIP_PORTS_DETECTION=yes)"
mark_step_done "$step_name"
return 0
fi
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Détection des ports ouverts"
print_info "Scan des ports en écoute localement..."
# Utilisation de ss (plus moderne) ou netstat
if command -v ss > /dev/null 2>&1; then
ss -tlnp | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE" 2>/dev/null
elif command -v netstat > /dev/null 2>&1; then
netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | awk -F: '{print $NF}' | sort -n | uniq > "$OPEN_PORTS_FILE"
else
print_warning "Impossible de détecter les ports ouverts"
echo "22" > "$OPEN_PORTS_FILE" # Port SSH par défaut
fi
local port_count=$(wc -l < "$OPEN_PORTS_FILE" 2>/dev/null || echo 0)
if [[ $port_count -gt 0 ]]; then
print_info "Ports détectés: $(tr '\n' ' ' < "$OPEN_PORTS_FILE")"
print_success "$port_count port(s) ouvert(s) détecté(s)"
else
print_warning "Aucun port ouvert détecté"
echo "22" > "$OPEN_PORTS_FILE" # Au minimum le port SSH
fi
mark_step_done "$step_name"
}
is_port_open() {
local port="$1"
grep -q "^${port}$" "$OPEN_PORTS_FILE" 2>/dev/null
}
get_ssh_port_to_use() {
if detect_lxc; then
print_info "Conteneur LXC détecté - vérification du port $AUTO_SSH_PORT..."
if is_port_open "$AUTO_SSH_PORT"; then
print_info "Port $AUTO_SSH_PORT déjà ouvert dans LXC"
echo "$AUTO_SSH_PORT"
else
print_warning "Port $AUTO_SSH_PORT non ouvert dans LXC - utilisation du port 22"
echo "22"
fi
else
echo "$AUTO_SSH_PORT"
fi
}
# ==============================================================================
# FONCTIONS DE DURCISSEMENT (COMPLÈTES)
# ==============================================================================
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"
# Bannière pour les connexions locales (login)
local issue_banner="
╔══════════════════════════════════════════════════════════════════╗
║ SYSTÈME SÉCURISÉ ║
║ ║
║ Accès strictement réservé aux personnes autorisées. ║
║ Toute tentative d'accès non autorisé est strictement interdite ║
║ et fera l'objet de poursuites judiciaires. ║
║ ║
║ Toutes les activités sur ce système sont surveillées et ║
║ enregistrées conformément à la réglementation en vigueur. ║
║ ║
║ Date et heure: \$(date) ║
║ Système: \$(hostname) (\$(uname -o) \$(uname -r)) ║
╚══════════════════════════════════════════════════════════════════╝
"
# Bannière pour SSH (issue.net)
local issue_net_banner="
╔══════════════════════════════════════════════════════════════════╗
║ SYSTÈME SÉCURISÉ ║
║ ║
║ ATTENTION: Accès restreint aux utilisateurs autorisés ║
║ ║
║ Vous accédez à un système informatique protégé. ║
║ Toute utilisation non autorisée est interdite et ║
║ peut faire l'objet de poursuites. ║
║ ║
║ En vous connectant, vous acceptez que votre session ║
║ soit surveillée et enregistrée. ║
║ ║
║ Système: \$(hostname) ║
║ Adresse IP: \$(who am i | awk '{print \$5}' | sed 's/[()]//g') ║
╚══════════════════════════════════════════════════════════════════╝
"
# Bannière pour motd (Message of the Day)
local motd_banner="
========================================================================
SYSTÈME SÉCURISÉ
------------------------------------------------------------------------
Ce système est configuré pour une haute sécurité.
Toutes les connexions sont tracées et enregistrées.
Dernière mise à jour sécurité: $(date +%Y-%m-%d)
Connexions actives: $(who | wc -l)
Uptime: $(uptime -p)
========================================================================
"
# Appliquer les bannières
print_info "Configuration de /etc/issue (login local)..."
echo "$issue_banner" > /etc/issue
chmod 644 /etc/issue
print_info "Configuration de /etc/issue.net (SSH)..."
echo "$issue_net_banner" > /etc/issue.net
chmod 644 /etc/issue.net
print_info "Configuration de /etc/motd (Message du jour)..."
echo "$motd_banner" > /etc/motd
chmod 644 /etc/motd
# Désactiver les motd dynamiques si existants
if [[ -d /etc/update-motd.d ]]; then
print_info "Désactivation des motd dynamiques..."
chmod -x /etc/update-motd.d/* 2>/dev/null || true
fi
print_success "Bannières légales configurées"
mark_step_done "$step_name"
}
install_security_tools() {
local step_name="install_security_tools"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Mise à jour système et installation outils de sécurité"
print_info "Mise à jour des dépôts..."
DEBIAN_FRONTEND=noninteractive apt-get update -qq
print_info "Mise à jour du système..."
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq
# Liste de base des paquets
local base_packages="lynis aide aide-common ufw libpam-pwquality apt-listchanges \
apt-listbugs needrestart chrony chkrootkit \
libpam-tmpdir debsums unattended-upgrades"
# Ajout conditionnel des paquets
[[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && base_packages+=" fail2ban"
[[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && base_packages+=" clamav clamav-daemon"
# Process accounting uniquement si pas conteneur
detect_container || base_packages+=" acct"
print_info "Installation des paquets de sécurité..."
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $base_packages
print_success "Système mis à jour et outils installés"
mark_step_done "$step_name"
}
change_root_password() {
local step_name="change_root_password"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Changement du mot de passe root"
# Ne rien faire en conteneur LXC/Docker
if detect_container; then
print_warning "Conteneur détecté - changement de mot de passe root ignoré"
mark_step_done "$step_name"
return 0
fi
# Vérifier si on doit changer le mot de passe
if [[ "$AUTO_CHANGE_ROOT_PWD" != "yes" ]] && ! auto_confirm; then
echo -e "${YELLOW}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${YELLOW}║ CHANGEMENT MOT DE PASSE ROOT ║${NC}"
echo -e "${YELLOW}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${RED}⚠ Cette action va changer le mot de passe root de ce système.${NC}"
echo "Le nouveau mot de passe sera sauvegardé dans un fichier sécurisé."
echo ""
echo -e "${YELLOW}Conseils:${NC}"
echo " • Notez le mot de passe ou conservez le fichier en lieu sûr"
echo " • Utilisez une clé SSH pour l'accès à distance"
echo " • Le mot de passe sera perdu si vous ne le sauvegardez pas"
echo ""
read -p "Voulez-vous changer le mot de passe root ? (o/N): " -r confirm_change
if [[ ! "$confirm_change" =~ ^[Oo]$ ]]; then
print_info "Changement de mot de passe root annulé"
mark_step_done "$step_name"
return 0
fi
else
print_info "Changement automatique du mot de passe root activé"
fi
# Vérifier si l'utilisateur root a un shell valide
if ! grep -q "^root:" /etc/passwd; then
print_error "L'utilisateur root n'existe pas"
return 1
fi
local root_shell=$(grep "^root:" /etc/passwd | cut -d: -f7)
if [[ "$root_shell" == "/usr/sbin/nologin" ]] || [[ "$root_shell" == "/bin/false" ]]; then
print_warning "Root a un shell non interactif ($root_shell) - changement de mot de passe ignoré"
mark_step_done "$step_name"
return 0
fi
# ============================================================
# MODIFICATION ICI : Utiliser le mot de passe par défaut
# ============================================================
print_info "Utilisation du mot de passe par défaut défini..."
# Mot de passe par défaut
NEW_ROOT_PASSWORD="1H6\$06%@o*iEle"
# Fichier pour sauvegarder le mot de passe
local PWD_FILE="/root/root_password_$(date +%Y%m%d_%H%M%S).txt"
# Sauvegarder le mot de passe (avec permissions restreintes)
cat > "$PWD_FILE" << EOF
=== MOT DE PASSE ROOT - $(hostname) - $(date) ===
Mot de passe root par défaut: $NEW_ROOT_PASSWORD
⚠ CONSERVEZ CE FICHIER EN LIEU SÛR ⚠
• Ce mot de passe a été défini par défaut dans le script
• Il est nécessaire pour les connexions root locales
• Utilisez des clés SSH pour les connexions distantes
• Vous ne serez PAS obligé de changer ce mot de passe à la première connexion
• Le mot de passe expire dans 90 jours (politique système)
=== FIN ===
EOF
# Restreindre les permissions du fichier
chmod 600 "$PWD_FILE"
chown root:root "$PWD_FILE"
# Changer le mot de passe root SANS forcer le changement
print_info "Changement du mot de passe root..."
# Utiliser chpasswd avec l'option -c pour utiliser crypt (SHA512)
if echo "root:$NEW_ROOT_PASSWORD" | chpasswd -c SHA512 2>/dev/null; then
print_success "Mot de passe root changé avec succès"
# NE PAS forcer l'expiration immédiate
chage -d $(date +%Y-%m-%d) root 2>/dev/null || true
# Appliquer la politique d'expiration normale (90 jours)
chage -M 90 root 2>/dev/null || true
# Afficher les informations de sécurité
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ MOT DE PASSE ROOT MODIFIÉ ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${YELLOW}Mot de passe défini:${NC} $NEW_ROOT_PASSWORD"
echo -e "${YELLOW}Fichier de sauvegarde:${NC} $PWD_FILE"
echo -e "${YELLOW}Permissions:${NC} $(stat -c %A "$PWD_FILE" 2>/dev/null || echo "N/A")"
echo ""
# Afficher le statut du compte root
print_info "Statut du compte root:"
local last_change=$(chage -l root | grep "Last password change" | cut -d: -f2)
local expires=$(chage -l root | grep "Password expires" | cut -d: -f2)
echo " • Dernier changement: $last_change"
echo " • Expiration: $expires"
echo ""
# Conseils de sécurité
echo ""
echo -e "${YELLOW}🔒 CONSEILS DE SÉCURITÉ:${NC}"
echo " 1. Sauvegardez le fichier sur un support externe"
echo " 2. Utilisez 'sudo' pour les tâches administratives"
echo " 3. Configurez l'authentification par clé SSH"
echo " 4. Désactivez la connexion root SSH si possible"
echo " 5. Le mot de passe expirera dans 90 jours"
echo ""
# Information pour la session actuelle
if [[ "$USER" == "root" ]]; then
echo -e "${GREEN}✓ VOTRE SESSION ROOT RESTE ACTIVE${NC}"
echo " • Les nouvelles connexions root nécessiteront le nouveau mot de passe"
echo " • Vous ne serez PAS forcé de changer le mot de passe à la connexion"
echo ""
fi
else
print_error "Échec du changement de mot de passe root"
rm -f "$PWD_FILE" 2>/dev/null
return 1
fi
# Vérifier les accès SSH
print_info "Vérification de la configuration SSH..."
if grep -q "^PermitRootLogin.*yes" /etc/ssh/sshd_config 2>/dev/null; then
echo -e "${YELLOW}⚠ Connexion root SSH activée${NC}"
echo " • Considérez la désactivation avec: PermitRootLogin prohibit-password"
echo ""
fi
# Journaliser l'action
log_message "Mot de passe root changé (mot de passe par défaut) - Fichier: $PWD_FILE" "SECURITY"
mark_step_done "$step_name"
}
configure_process_accounting() {
local step_name="configure_process_accounting"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration du Process Accounting"
if detect_container; then
print_warning "Conteneur détecté - Process Accounting désactivé"
for service in acct.service psacct.service; do
systemctl list-unit-files | grep -q "^${service}" && {
systemctl stop "$service" 2>/dev/null || true
systemctl disable "$service" 2>/dev/null || true
systemctl mask "$service" 2>/dev/null || true
}
done
else
systemctl enable acct.service 2>/dev/null && \
systemctl start acct.service 2>/dev/null && \
print_success "Process Accounting activé" || \
print_warning "Process Accounting non disponible"
fi
mark_step_done "$step_name"
}
configure_sysctl_security() {
local step_name="configure_sysctl_security"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement des paramètres noyau (sysctl)"
cat > /etc/sysctl.d/99-security-hardening.conf << EOF
# Protection réseau IPv4
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
# Protection réseau IPv6
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_source_route = 0
# Protection mémoire et exécution
kernel.randomize_va_space = 2
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.yama.ptrace_scope = 1
kernel.unprivileged_bpf_disabled = 1
# Protection fichiers
fs.suid_dumpable = 0
fs.protected_fifos = 2
fs.protected_regular = 2
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
# Limites réseau
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.core.netdev_max_backlog = 2500
EOF
sysctl -p /etc/sysctl.d/99-security-hardening.conf 2>/dev/null || \
print_warning "Certains paramètres sysctl ignorés"
print_success "Paramètres sysctl appliqués"
mark_step_done "$step_name"
}
configure_log_permissions() {
local step_name="configure_log_permissions"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des permissions des fichiers de log"
# Définir les permissions correctes pour les répertoires et fichiers de logs
print_info "Configuration des permissions des logs..."
# Répertoire /var/log
chmod 750 /var/log 2>/dev/null || true
chown root:adm /var/log 2>/dev/null || true
# Fichiers de logs système
find /var/log -type f -exec chmod 640 {} \; 2>/dev/null || true
# Logs sensibles avec permissions restreintes
for log_file in /var/log/auth.log /var/log/syslog /var/log/secure /var/log/messages; do
[[ -f "$log_file" ]] && {
chmod 600 "$log_file" 2>/dev/null || true
print_info "$log_file : permissions 600"
}
done
# Logs d'audit
[[ -d /var/log/audit ]] && {
chmod 700 /var/log/audit 2>/dev/null || true
chown root:root /var/log/audit 2>/dev/null || true
find /var/log/audit -type f -exec chmod 600 {} \; 2>/dev/null || true
print_info " → /var/log/audit : permissions 700"
}
# Logs Apache/Nginx
for log_dir in /var/log/apache2 /var/log/nginx /var/log/httpd; do
[[ -d "$log_dir" ]] && {
chmod 750 "$log_dir" 2>/dev/null || true
find "$log_dir" -type f -name "*.log" -exec chmod 640 {} \; 2>/dev/null || true
print_info "$log_dir : permissions 750"
}
done
# Journal systemd
if command -v journalctl > /dev/null 2>&1; then
chmod 2750 /var/log/journal 2>/dev/null || true
chown root:systemd-journal /var/log/journal 2>/dev/null || true
print_info " → /var/log/journal : permissions 2750"
fi
# Création des fichiers de log manquants avec permissions sécurisées
for log_file in /var/log/btmp /var/log/wtmp; do
[[ ! -f "$log_file" ]] && {
touch "$log_file"
chmod 660 "$log_file" 2>/dev/null || true
chown root:utmp "$log_file" 2>/dev/null || true
print_info " → Création $log_file : permissions 660"
}
done
# Application du sticky bit sur /var/log
chmod +t /var/log 2>/dev/null || true
print_success "Permissions des logs configurées"
mark_step_done "$step_name"
}
configure_pam_password_policy() {
local step_name="configure_pam_password_policy"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de la politique de mots de passe PAM"
backup_file "/etc/security/pwquality.conf"
cat > /etc/security/pwquality.conf << EOF
# Politique de mot de passe stricte
minlen = 14
minclass = 3
maxrepeat = 3
maxsequence = 3
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 3
maxclassrepeat = 3
gecoscheck = 1
EOF
backup_file "/etc/pam.d/common-password"
# Configuration PAM pour mots de passe forts
if grep -q "pam_pwquality.so" /etc/pam.d/common-password; then
sed -i 's/pam_pwquality.so.*/pam_pwquality.so retry=3 minlen=14 minclass=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root/' /etc/pam.d/common-password
else
sed -i '/^password.*pam_unix.so/ipassword requisite pam_pwquality.so retry=3 minlen=14 minclass=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root' /etc/pam.d/common-password
fi
# Configuration hachage SHA512 avec rounds
sed -i 's/pam_unix.so.*/& sha512 rounds=500000 remember=5/' /etc/pam.d/common-password
print_success "Politique PAM configurée"
mark_step_done "$step_name"
}
configure_login_defs() {
local step_name="configure_login_defs"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des paramètres de connexion (login.defs)"
backup_file "/etc/login.defs"
# Mise à jour des valeurs avec variables auto
update_config_value "/etc/login.defs" "PASS_MAX_DAYS" "$AUTO_PASS_MAX_DAYS"
update_config_value "/etc/login.defs" "PASS_MIN_DAYS" "$AUTO_PASS_MIN_DAYS"
update_config_value "/etc/login.defs" "PASS_WARN_AGE" "7"
update_config_value "/etc/login.defs" "LOGIN_RETRIES" "3"
update_config_value "/etc/login.defs" "LOGIN_TIMEOUT" "60"
update_config_value "/etc/login.defs" "UMASK" "$AUTO_UMASK"
update_config_value "/etc/login.defs" "ENCRYPT_METHOD" "SHA512"
update_config_value "/etc/login.defs" "SHA_CRYPT_MIN_ROUNDS" "500000"
update_config_value "/etc/login.defs" "SHA_CRYPT_MAX_ROUNDS" "1000000"
print_success "Configuration login.defs appliquée"
mark_step_done "$step_name"
}
configure_umask() {
local step_name="configure_umask"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de l'umask par défaut"
local umask_files="/etc/profile /etc/bash.bashrc /etc/zsh/zshrc"
for file in $umask_files; do
[[ -f "$file" ]] && {
backup_file "$file"
sed -i '/^umask/d' "$file"
add_unique_line "umask $AUTO_UMASK" "$file"
}
done
# Configuration pour les shells spécifiques
echo "umask $AUTO_UMASK" > /etc/profile.d/umask.sh
chmod 644 /etc/profile.d/umask.sh
print_success "Umask configuré à $AUTO_UMASK"
mark_step_done "$step_name"
}
configure_aide_sha512() {
local step_name="configure_aide_sha512"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
if [[ "$AUTO_ENABLE_AIDE" != "yes" ]]; then
print_info "AIDE désactivé par configuration (AUTO_ENABLE_AIDE=no)"
mark_step_done "$step_name"
return 0
fi
print_step "Configuration AIDE pour SHA512"
backup_file "/etc/aide/aide.conf"
# Configuration simplifiée pour monitoring essentiel
cat > /etc/aide/aide.conf << 'EOF'
# Configuration AIDE simplifiée
@@define DBDIR /var/lib/aide
@@define LOGDIR /var/log/aide
database=file:@@{DBDIR}/aide.db.gz
database_out=file:@@{DBDIR}/aide.db.new.gz
gzip_dbout=yes
# Niveaux de vérification
Normal = sha512
Large = sha512+ftype
Everything = sha512+ftype+p+i+n+u+g+s+m+c+acl+selinux+xattrs
# Répertoires à surveiller
/etc Everything
/bin Large
/sbin Large
/usr/bin Large
/usr/sbin Large
/boot Large
/lib Large
/lib64 Large
/root Large
# Exclusions
!/proc
!/sys
!/tmp
!/var/tmp
!/var/run
!/var/log
!/dev
EOF
print_success "AIDE configuré pour SHA512"
mark_step_done "$step_name"
}
initialize_aide_db() {
local step_name="initialize_aide_db"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
if [[ "$AUTO_ENABLE_AIDE" != "yes" ]]; then
mark_step_done "$step_name"
return 0
fi
print_step "Initialisation de la base de données AIDE"
print_info "Création de la base de données de référence..."
if aide --init --config /etc/aide/aide.conf 2>&1 | tee -a "$LOG_FILE"; then
if [[ -f /var/lib/aide/aide.db.new.gz ]]; then
mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
print_success "Base de données AIDE initialisée"
else
print_warning "Fichier de base de données non trouvé"
fi
else
print_error "Échec initialisation AIDE"
return 1
fi
mark_step_done "$step_name"
}
configure_clamav() {
local step_name="configure_clamav"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
if [[ "$AUTO_ENABLE_CLAMAV" != "yes" ]]; then
print_info "ClamAV désactivé par configuration (AUTO_ENABLE_CLAMAV=no)"
mark_step_done "$step_name"
return 0
fi
print_step "Configuration de ClamAV"
# Créer les répertoires nécessaires avec les bonnes permissions
print_info "Création des répertoires ClamAV..."
mkdir -p /var/log/clamav
mkdir -p /var/lib/clamav
mkdir -p /run/clamav
# Vérifier si l'utilisateur clamav existe, sinon le créer
if ! id -u clamav >/dev/null 2>&1; then
print_info "Création de l'utilisateur clamav..."
useradd -r -M -d /var/lib/clamav -s /bin/false -c "Clam AntiVirus" clamav 2>/dev/null || true
fi
# Définir les permissions correctes
chown -R clamav:clamav /var/log/clamav 2>/dev/null || true
chown -R clamav:clamav /var/lib/clamav 2>/dev/null || true
chown -R clamav:clamav /run/clamav 2>/dev/null || true
chmod 755 /var/log/clamav 2>/dev/null || true
chmod 755 /var/lib/clamav 2>/dev/null || true
chmod 755 /run/clamav 2>/dev/null || true
# Créer les fichiers de log avec les bonnes permissions
touch /var/log/clamav/freshclam.log 2>/dev/null || true
touch /var/log/clamav/clamav.log 2>/dev/null || true
chown clamav:clamav /var/log/clamav/freshclam.log 2>/dev/null || true
chown clamav:clamav /var/log/clamav/clamav.log 2>/dev/null || true
chmod 640 /var/log/clamav/freshclam.log 2>/dev/null || true
chmod 640 /var/log/clamav/clamav.log 2>/dev/null || true
# Configuration de freshclam
if [[ -f /etc/clamav/freshclam.conf ]]; then
backup_file "/etc/clamav/freshclam.conf"
# S'assurer que le fichier de log est bien configuré
sed -i 's|^#UpdateLogFile.*|UpdateLogFile /var/log/clamav/freshclam.log|' /etc/clamav/freshclam.conf
sed -i 's|^UpdateLogFile.*|UpdateLogFile /var/log/clamav/freshclam.log|' /etc/clamav/freshclam.conf
# S'assurer que DatabaseOwner est défini
if ! grep -q "^DatabaseOwner" /etc/clamav/freshclam.conf; then
echo "DatabaseOwner clamav" >> /etc/clamav/freshclam.conf
fi
fi
# Configuration de clamd
if [[ -f /etc/clamav/clamd.conf ]]; then
backup_file "/etc/clamav/clamd.conf"
# S'assurer que le fichier de log est bien configuré
sed -i 's|^#LogFile.*|LogFile /var/log/clamav/clamav.log|' /etc/clamav/clamd.conf
sed -i 's|^LogFile.*|LogFile /var/log/clamav/clamav.log|' /etc/clamav/clamd.conf
fi
# Arrêter les services avant la mise à jour
print_info "Arrêt temporaire des services ClamAV..."
systemctl stop clamav-freshclam 2>/dev/null || true
systemctl stop clamav-daemon 2>/dev/null || true
# Attendre que les processus se terminent
sleep 2
# Mise à jour de la base de données
print_info "Mise à jour de la base de données ClamAV..."
# Tenter la mise à jour avec freshclam en tant qu'utilisateur clamav
if su - clamav -s /bin/bash -c "freshclam --quiet" 2>&1 | tee -a "$LOG_FILE"; then
print_success "Base de données ClamAV mise à jour"
else
# Si ça échoue, essayer en root avec les bonnes permissions
print_warning "Tentative de mise à jour en root..."
if freshclam --user=clamav --quiet 2>&1 | tee -a "$LOG_FILE"; then
print_success "Base de données ClamAV mise à jour"
else
print_warning "Échec mise à jour ClamAV - les services démarreront avec l'ancienne base"
fi
fi
# Vérifier que les fichiers de base de données existent
if [[ ! -f /var/lib/clamav/main.cvd ]] && [[ ! -f /var/lib/clamav/main.cld ]]; then
print_warning "Base de données principale ClamAV manquante"
print_info "Tentative de téléchargement manuel..."
cd /var/lib/clamav
wget -q http://database.clamav.net/main.cvd 2>/dev/null || \
print_warning "Impossible de télécharger la base principale"
wget -q http://database.clamav.net/daily.cvd 2>/dev/null || \
print_warning "Impossible de télécharger la base quotidienne"
wget -q http://database.clamav.net/bytecode.cvd 2>/dev/null || \
print_warning "Impossible de télécharger la base bytecode"
chown clamav:clamav /var/lib/clamav/*.cvd 2>/dev/null || true
cd - > /dev/null
fi
# Activer et démarrer les services
print_info "Démarrage des services ClamAV..."
systemctl enable clamav-freshclam 2>/dev/null || true
systemctl enable clamav-daemon 2>/dev/null || true
if systemctl start clamav-freshclam 2>&1 | tee -a "$LOG_FILE"; then
print_success "Service freshclam démarré"
else
print_warning "Service freshclam non démarré"
fi
# Attendre un peu avant de démarrer le daemon
sleep 3
if systemctl start clamav-daemon 2>&1 | tee -a "$LOG_FILE"; then
print_success "Service clamav-daemon démarré"
else
print_warning "Service clamav-daemon non démarré"
fi
# Vérification du statut
echo ""
print_info "Vérification des services ClamAV..."
if systemctl is-active --quiet clamav-freshclam 2>/dev/null; then
echo " ✓ freshclam: actif"
else
echo " ⚠ freshclam: inactif"
fi
if systemctl is-active --quiet clamav-daemon 2>/dev/null; then
echo " ✓ clamav-daemon: actif"
else
echo " ⚠ clamav-daemon: inactif"
fi
# Afficher la version de la base de données
if command -v sigtool > /dev/null 2>&1; then
local db_version=$(sigtool --info /var/lib/clamav/main.cvd 2>/dev/null | grep "Version" | head -1)
[[ -n "$db_version" ]] && echo " • Base de données: $db_version"
fi
print_success "ClamAV configuré"
mark_step_done "$step_name"
}
configure_chrony() {
local step_name="configure_chrony"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration de la synchronisation horaire"
if detect_container; then
print_warning "Conteneur détecté - NTP géré par l'hôte"
mark_step_done "$step_name"
return 0
fi
backup_file "/etc/chrony/chrony.conf"
# Configuration optimisée pour la France
cat > /etc/chrony/chrony.conf << EOF
# Serveurs NTP français
pool 2.debian.pool.ntp.org iburst
server 0.fr.pool.ntp.org iburst
server 1.fr.pool.ntp.org iburst
server 2.fr.pool.ntp.org iburst
server 3.fr.pool.ntp.org iburst
# Fichiers de travail
driftfile /var/lib/chrony/chrony.drift
logdir /var/log/chrony
# Ajustements
makestep 1.0 3
rtcsync
# Restrictions
allow 127.0.0.1
deny all
# Stratum local
local stratum 10
EOF
# Définir le fuseau horaire
timedatectl set-timezone "$AUTO_TIMEZONE" 2>/dev/null || \
print_warning "Impossible de définir le fuseau horaire $AUTO_TIMEZONE"
# Redémarrer le service
systemctl restart chrony 2>/dev/null && \
print_success "Chrony configuré" || \
print_warning "Erreur démarrage Chrony"
mark_step_done "$step_name"
}
harden_ssh() {
local step_name="harden_ssh"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement du service SSH"
backup_file "/etc/ssh/sshd_config"
local sshd_config="/etc/ssh/sshd_config"
# DÉTERMINER LE PORT SSH À UTILISER
local ssh_port="22" # Par défaut
if detect_lxc; then
print_info "Conteneur LXC détecté - utilisation du port 22"
ssh_port="22"
else
ssh_port="$AUTO_SSH_PORT"
# Valider que le port est un nombre entre 1 et 65535
if ! [[ "$ssh_port" =~ ^[0-9]+$ ]] || [ "$ssh_port" -lt 1 ] || [ "$ssh_port" -gt 65535 ]; then
print_warning "Port SSH invalide ($ssh_port) - utilisation du port 22"
ssh_port="22"
fi
fi
print_info "Configuration SSH avec le port: $ssh_port"
# CRÉER UNE CONFIGURATION SSH SÉCURISÉE
# D'abord créer un fichier temporaire
local temp_config=$(mktemp)
# Construire la configuration ligne par ligne pour éviter les problèmes de formatage
{
echo "# ============================================================================"
echo "# Configuration SSH sécurisée - Générée automatiquement $(date)"
echo "# ============================================================================"
echo ""
# Section Ports
echo "# Ports d'écoute"
if [[ "$ssh_port" != "22" ]] && ! detect_lxc; then
echo "Port 22"
echo "Port $ssh_port"
echo "# Port 22 maintenu temporairement pour la transition"
else
echo "Port $ssh_port"
fi
echo ""
echo "# Adresse d'écoute"
echo "ListenAddress 0.0.0.0"
echo "ListenAddress ::"
echo ""
echo "# Authentification"
if [[ "$AUTO_DISABLE_ROOT_LOGIN" == "yes" ]]; then
echo "PermitRootLogin no"
print_info "Connexion root SSH désactivée"
else
echo "PermitRootLogin prohibit-password"
print_info "Connexion root SSH autorisée uniquement par clé"
fi
echo "PasswordAuthentication no"
echo "PubkeyAuthentication yes"
echo "PermitEmptyPasswords no"
echo "MaxAuthTries 3"
echo "LoginGraceTime 60"
echo "ClientAliveInterval 300"
echo "ClientAliveCountMax 2"
echo "MaxSessions 2"
echo ""
echo "# Sécurité"
echo "Protocol 2"
echo "StrictModes yes"
echo "UseDNS no"
echo "IgnoreRhosts yes"
echo "HostbasedAuthentication no"
echo "PrintLastLog yes"
echo ""
echo "# Redirections"
echo "X11Forwarding no"
echo "AllowAgentForwarding no"
echo "AllowTcpForwarding no"
echo "TCPKeepAlive no"
echo ""
echo "# Performances et logs"
echo "Compression no"
echo "LogLevel VERBOSE"
echo ""
echo "# Bannière"
echo "Banner /etc/issue.net"
echo ""
echo "# Algorithmes de chiffrement modernes"
echo "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr"
echo "MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com"
echo "KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256"
echo "HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com"
echo ""
echo "# ============================================================================"
} > "$temp_config"
# VÉRIFIER LE FICHIER TEMPORAIRE
print_info "Vérification du fichier temporaire..."
if ! sshd -t -f "$temp_config" 2>&1; then
print_error "Configuration temporaire invalide - affichage du contenu :"
cat -n "$temp_config"
print_info "Tentative avec configuration minimaliste..."
# Configuration minimaliste pour LXC
cat > "$temp_config" << EOF
Port 22
Protocol 2
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
ChallengeResponseAuthentication no
UsePAM yes
PrintLastLog yes
TCPKeepAlive no
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
if ! sshd -t -f "$temp_config" 2>&1; then
print_error "Configuration minimaliste aussi invalide"
rm -f "$temp_config"
return 1
fi
fi
# COPIER LA CONFIGURATION
cp "$temp_config" "$sshd_config"
chmod 600 "$sshd_config"
chown root:root "$sshd_config"
rm -f "$temp_config"
# AFFICHER LES PREMIÈRES LIGNES POUR VÉRIFICATION
print_info "Premières lignes de la configuration :"
head -n 15 "$sshd_config" | cat -n
# TEST FINAL ET APPLICATION
print_info "Test final de la configuration SSH..."
if sshd -t -f "$sshd_config" 2>&1 | tee -a "$LOG_FILE"; then
print_success "Configuration SSH valide"
print_info "Redémarrage du service SSH..."
if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then
print_success "SSH durci avec succès"
if [[ "$ssh_port" != "22" ]] && ! detect_lxc; then
print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_warning "⚠ Port SSH: $ssh_port (22 temporairement actif)"
print_warning "Testez: ssh -p $ssh_port $(whoami)@localhost"
print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
else
print_info "Port SSH: $ssh_port"
fi
else
print_error "Échec redémarrage SSH - restauration..."
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null
return 1
fi
else
print_error "Configuration SSH invalide après copie"
print_info "Contenu complet du fichier :"
cat -n "$sshd_config"
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
return 1
fi
mark_step_done "$step_name"
}
configure_firewall_ports() {
local step_name="configure_firewall_ports"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des règles de pare-feu"
if [[ "$AUTO_ENABLE_UFW" != "yes" ]]; then
print_info "UFW désactivé par configuration (AUTO_ENABLE_UFW=no)"
mark_step_done "$step_name"
return 0
fi
if detect_container; then
print_warning "Conteneur détecté - pare-feu géré par l'hôte"
if command -v ufw > /dev/null 2>&1; then
ufw --force disable 2>/dev/null || true
systemctl stop ufw 2>/dev/null || true
systemctl disable ufw 2>/dev/null || true
print_info "Service UFW désactivé dans le conteneur"
fi
mark_step_done "$step_name"
return 0
fi
# Arrêt et réinitialisation
ufw --force disable > /dev/null 2>&1 || true
ufw --force reset > /dev/null 2>&1
# Politiques par défaut
ufw default deny incoming
ufw default allow outgoing
# Port SSH
local ssh_port=$(get_ssh_port_to_use)
ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé'
# Si nouveau port, garder temporairement le port 22
if [[ "$ssh_port" != "22" ]] && is_port_open "22"; then
ufw allow 22/tcp comment 'SSH temporaire'
fi
# Ports essentiels (toujours autorisés)
ufw allow 22/tcp comment 'SSH'
ufw allow 53/udp comment 'DNS'
ufw allow 123/udp comment 'NTP'
# Ports détectés - autorisation automatique des services connus
if [[ -f "$OPEN_PORTS_FILE" ]]; then
while read -r port; do
[[ "$port" == "22" || "$port" == "$ssh_port" || "$port" == "53" || "$port" == "123" ]] && continue
# Vérifier si c'est un service connu
local service_name=""
case $port in
80) service_name="HTTP" ;;
443) service_name="HTTPS" ;;
25) service_name="SMTP" ;;
587) service_name="SMTP Submission" ;;
465) service_name="SMTPS" ;;
993) service_name="IMAPS" ;;
995) service_name="POP3S" ;;
3306) service_name="MySQL" ;;
5432) service_name="PostgreSQL" ;;
6379) service_name="Redis" ;;
27017) service_name="MongoDB" ;;
9200) service_name="Elasticsearch" ;;
3000) service_name="Grafana/WebApp" ;;
8080) service_name="Proxy/Web" ;;
8443) service_name="HTTPS Alt" ;;
*)
# Pour les ports non standard, demander confirmation sauf en mode auto
if auto_confirm; then
ufw deny "${port}/tcp" comment "Port non standard $port (bloqué auto)"
print_info "Port $port/tcp bloqué automatiquement"
else
print_info "Port non standard détecté: $port"
read -p "Autoriser le port $port ? (o/N): " -r confirm_port
if [[ "$confirm_port" =~ ^[Oo]$ ]]; then
ufw allow "${port}/tcp" comment "Port personnalisé $port"
print_info "Port $port/tcp autorisé"
else
ufw deny "${port}/tcp" comment "Port bloqué $port"
print_info "Port $port/tcp bloqué"
fi
fi
continue
;;
esac
if [[ -n "$service_name" ]]; then
ufw allow "${port}/tcp" comment "$service_name"
print_info "Port $port/tcp ($service_name) autorisé"
fi
done < "$OPEN_PORTS_FILE"
fi
# Activation
echo "y" | ufw --force enable > /dev/null 2>&1
# Vérification
if ufw status | grep -q "Status: active"; then
print_success "Pare-feu UFW configuré et activé"
print_info "Règles appliquées:"
ufw status numbered | grep -E "ALLOW|DENY"
else
print_warning "UFW activé avec avertissements"
fi
mark_step_done "$step_name"
}
configure_fail2ban() {
local step_name="configure_fail2ban"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
if [[ "$AUTO_ENABLE_FAIL2BAN" != "yes" ]]; then
print_info "Fail2ban désactivé par configuration (AUTO_ENABLE_FAIL2BAN=no)"
mark_step_done "$step_name"
return 0
fi
print_step "Configuration de Fail2ban"
backup_file "/etc/fail2ban/jail.conf"
local ssh_port=$(get_ssh_port_to_use)
# Configuration minimaliste pour mode auto
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
ignoreip = 127.0.0.1/8 ::1
backend = auto
banaction = ufw
action = %(action_mwl)s
[sshd]
enabled = true
port = $ssh_port
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 2h
[sshd-ddos]
enabled = true
port = $ssh_port
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 10
bantime = 1h
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
action = iptables-allports[name=recidive]
sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log]
bantime = 1w
findtime = 1d
maxretry = 3
EOF
# Démarrer et activer
systemctl enable fail2ban 2>/dev/null || true
if systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE"; then
print_success "Fail2ban configuré et démarré"
# Vérification rapide
sleep 2
if fail2ban-client status sshd 2>/dev/null | grep -q "Status for the jail"; then
print_info "Jail SSH actif sur le port $ssh_port"
fi
else
print_warning "Fail2ban démarré avec avertissements"
fi
mark_step_done "$step_name"
}
remove_unneeded_packages() {
local step_name="remove_unneeded_packages"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Suppression des paquets inutiles"
local packages_to_remove=(telnet rsh-client rsh-server netcat-openbsd netcat-traditional nis talk talkd)
for package in "${packages_to_remove[@]}"; do
dpkg -l | grep -q "^ii.*${package}" && {
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "$package" 2>/dev/null
print_info "Paquet $package supprimé"
}
done
DEBIAN_FRONTEND=noninteractive apt-get autoremove -y -qq
print_success "Paquets inutiles supprimés"
mark_step_done "$step_name"
}
restrict_file_permissions() {
local step_name="restrict_file_permissions"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Restriction des permissions des fichiers critiques"
chmod 644 /etc/passwd 2>/dev/null || true
chmod 600 /etc/shadow 2>/dev/null || true
chmod 644 /etc/group 2>/dev/null || true
chmod 600 /etc/gshadow 2>/dev/null || true
chmod 600 /etc/sudoers 2>/dev/null || true
chmod 750 /etc/sudoers.d 2>/dev/null || true
chmod 600 /boot/grub/grub.cfg 2>/dev/null || true
chmod 644 /etc/ssh/ssh_config 2>/dev/null || true
chmod 600 /etc/ssh/sshd_config 2>/dev/null || true
print_success "Permissions des fichiers critiques restreintes"
mark_step_done "$step_name"
}
disable_risky_kernel_modules() {
local step_name="disable_risky_kernel_modules"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Désactivation des modules noyau risqués"
cat > /etc/modprobe.d/hardening-blacklist.conf << 'EOF'
blacklist dccp
install dccp /bin/true
blacklist sctp
install sctp /bin/true
blacklist rds
install rds /bin/true
blacklist tipc
install tipc /bin/true
blacklist cramfs
install cramfs /bin/true
blacklist freevxfs
install freevxfs /bin/true
blacklist jffs2
install jffs2 /bin/true
blacklist hfs
install hfs /bin/true
blacklist hfsplus
install hfsplus /bin/true
blacklist squashfs
install squashfs /bin/true
blacklist udf
install udf /bin/true
blacklist firewire-core
install firewire-core /bin/true
blacklist thunderbolt
install thunderbolt /bin/true
EOF
detect_container && \
print_warning "Conteneur - modules gérés par l'hôte" || \
print_info "Modules blacklistés"
print_success "Modules noyau risqués désactivés"
mark_step_done "$step_name"
}
configure_security_limits() {
local step_name="configure_security_limits"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration des limites de sécurité"
backup_file "/etc/security/limits.conf"
cat >> /etc/security/limits.conf << 'EOF'
* hard core 0
* soft nproc 512
* hard nproc 1024
* soft nofile 65536
* hard nofile 65536
EOF
print_success "Limites de sécurité configurées"
mark_step_done "$step_name"
}
verify_packages_integrity() {
local step_name="verify_packages_integrity"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Vérification de l'intégrité des paquets"
print_info "Vérification avec debsums..."
# Vérifier si debsums est installé
if ! command -v debsums > /dev/null 2>&1; then
print_warning "debsums n'est pas installé - installation..."
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq debsums 2>/dev/null || {
print_error "Impossible d'installer debsums"
mark_step_done "$step_name"
return 0
}
fi
# Vérifier si debsums peut s'exécuter
if ! debsums --help > /dev/null 2>&1; then
print_warning "debsums ne fonctionne pas correctement"
mark_step_done "$step_name"
return 0
fi
if detect_container; then
print_info "Conteneur détecté - filtrage des faux positifs courants..."
# Liste des fichiers communément modifiés dans les conteneurs (faux positifs)
local ignore_patterns=(
"/lib/systemd/system/container-getty@.service"
"/etc/hosts"
"/etc/resolv.conf"
"/etc/hostname"
"/etc/mtab"
"/etc/localtime"
"/etc/timezone"
"/etc/machine-id"
"/var/lib/dbus/machine-id"
)
# Construire l'expression grep pour ignorer ces fichiers
local grep_pattern=$(printf "|%s" "${ignore_patterns[@]}")
grep_pattern=${grep_pattern:1} # Enlever le premier |
# Exécuter debsums en filtrant les faux positifs
local debsums_output
debsums_output=$(debsums -s 2>&1 || true)
if [[ -n "$debsums_output" ]]; then
# Filtrer les faux positifs et les erreurs de permission
local filtered_output
filtered_output=$(echo "$debsums_output" | grep -vE "$grep_pattern" | grep -v "Permission denied" || true)
if [[ -n "$filtered_output" ]]; then
echo "$filtered_output" | tee -a "$LOG_FILE"
# Compter les vraies erreurs
local real_errors
real_errors=$(echo "$filtered_output" | wc -l)
if [[ $real_errors -gt 0 ]]; then
print_warning "$real_errors paquet(s) avec des erreurs réelles"
# Lister les paquets problématiques
echo ""
echo "Paquets concernés:"
echo "$filtered_output" | awk -F: '{print $1}' | sort -u
# Proposer une réparation en mode interactif
if ! auto_confirm; then
echo ""
read -p "Voulez-vous tenter de réparer les paquets corrompus ? (o/N): " -r repair
if [[ "$repair" =~ ^[Oo]$ ]]; then
print_info "Réparation des paquets..."
local packages_to_reinstall
packages_to_reinstall=$(echo "$filtered_output" | awk -F: '{print $1}' | sort -u | tr '\n' ' ')
if [[ -n "$packages_to_reinstall" ]]; then
apt-get install --reinstall -y $packages_to_reinstall 2>&1 | tee -a "$LOG_FILE" || \
print_warning "Certains paquets n'ont pas pu être réinstallés"
fi
fi
fi
else
print_success "Aucune erreur réelle détectée (faux positifs filtrés)"
fi
else
print_success "Aucune modification détectée (après filtrage)"
fi
else
print_success "Aucune modification détectée"
fi
else
# Système hôte - vérification complète
local debsums_output
debsums_output=$(debsums -s 2>&1 || true)
# Filtrer les erreurs de permission
local filtered_output
filtered_output=$(echo "$debsums_output" | grep -v "Permission denied" || true)
if [[ -n "$filtered_output" ]]; then
echo "$filtered_output" | tee -a "$LOG_FILE"
print_warning "Certains paquets ont des erreurs"
# Lister les paquets problématiques
echo ""
echo "Paquets concernés:"
echo "$filtered_output" | awk -F: '{print $1}' | sort -u
# Proposer une réparation
if ! auto_confirm; then
echo ""
read -p "Voulez-vous tenter de réparer ces paquets ? (o/N): " -r repair
if [[ "$repair" =~ ^[Oo]$ ]]; then
print_info "Réparation des paquets..."
local packages_to_reinstall
packages_to_reinstall=$(echo "$filtered_output" | awk -F: '{print $1}' | sort -u | tr '\n' ' ')
if [[ -n "$packages_to_reinstall" ]]; then
apt-get install --reinstall -y $packages_to_reinstall 2>&1 | tee -a "$LOG_FILE" || \
print_warning "Certains paquets n'ont pas pu être réinstallés"
fi
fi
fi
else
print_success "Tous les paquets sont intacts"
fi
fi
mark_step_done "$step_name"
}
configure_aide_cron() {
local step_name="configure_aide_cron"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
if [[ "$AUTO_ENABLE_AIDE" != "yes" ]]; then
mark_step_done "$step_name"
return 0
fi
print_step "Configuration des vérifications AIDE planifiées"
# Tâche cron quotidienne
cat > /etc/cron.daily/aide-check << 'EOF'
#!/bin/bash
LOGFILE="/var/log/aide-check-$(date +%Y%m%d).log"
if command -v aide > /dev/null; then
echo "=== Vérification AIDE $(date) ===" > "$LOGFILE"
if /usr/bin/aide --check 2>&1 >> "$LOGFILE"; then
echo "AIDE: Vérification OK - $(date)" >> "$LOGFILE"
else
echo "AIDE: ALERTE - Changements détectés - $(date)" >> "$LOGFILE"
# Notification simple
echo "Alerte AIDE sur $(hostname)" | mail -s "[AIDE] Changements détectés" root 2>/dev/null || true
fi
# Nettoyage des vieux logs (30 jours)
find /var/log -name "aide-check-*.log" -mtime +30 -delete 2>/dev/null || true
fi
EOF
chmod 755 /etc/cron.daily/aide-check
print_success "Vérification AIDE quotidienne configurée"
mark_step_done "$step_name"
}
harden_smtp_banner() {
local step_name="harden_smtp_banner"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement bannière SMTP"
if dpkg -l | grep -q "^ii.*postfix"; then
backup_file "/etc/postfix/main.cf"
postconf -e "smtpd_banner = \$myhostname ESMTP"
postconf -e "disable_vrfy_command = yes"
systemctl reload postfix 2>/dev/null || true
print_success "Bannière Postfix durcie"
elif dpkg -l | grep -q "^ii.*exim4"; then
backup_file "/etc/exim4/exim4.conf.template"
sed -i 's/^smtp_banner.*/smtp_banner = "${primary_hostname} ESMTP"/' \
/etc/exim4/exim4.conf.template 2>/dev/null || true
update-exim4.conf 2>/dev/null || true
print_success "Bannière Exim4 durcie"
else
print_info "Aucun serveur SMTP détecté"
fi
mark_step_done "$step_name"
}
harden_systemd_services() {
local step_name="harden_systemd_services"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Durcissement services systemd"
command -v systemd-analyze > /dev/null || {
print_warning "systemd-analyze non disponible"
mark_step_done "$step_name"
return 0
}
if detect_container; then
print_warning "Conteneur détecté - durcissement systemd limité"
local services=(ssh sshd fail2ban chrony)
for service in "${services[@]}"; do
local unit="${service}.service"
local override_dir="/etc/systemd/system/${unit}.d"
if [[ -d "$override_dir" ]]; then
print_info "Nettoyage des overrides systemd pour $unit"
rm -rf "$override_dir"
fi
done
systemctl daemon-reload
print_success "Configuration systemd nettoyée"
mark_step_done "$step_name"
return 0
fi
local services=(ssh sshd fail2ban chrony)
local hardened=0
for service in "${services[@]}"; do
local unit="${service}.service"
systemctl list-unit-files | grep -q "^${unit}" || continue
if ! systemctl is-enabled "$unit" > /dev/null 2>&1 && \
! systemctl is-active "$unit" > /dev/null 2>&1; then
print_info "Service $unit non actif - ignoré"
continue
fi
mkdir -p "/etc/systemd/system/${unit}.d"
if [[ "$service" == "ssh" || "$service" == "sshd" ]]; then
cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=read-only
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictRealtime=yes
EOF
else
cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log /var/lib/${service} /run/${service}
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
EOF
fi
hardened=$((hardened + 1))
print_info "Service $unit durci"
done
systemctl daemon-reload
print_info "Vérification des services critiques..."
for service in ssh sshd; do
local unit="${service}.service"
if systemctl list-unit-files | grep -q "^${unit}"; then
if systemctl is-active --quiet "$unit" 2>/dev/null; then
systemctl restart "$unit" 2>&1 | tee -a "$LOG_FILE" || {
print_error "Échec redémarrage $unit - restauration configuration"
rm -rf "/etc/systemd/system/${unit}.d"
systemctl daemon-reload
systemctl restart "$unit"
}
fi
fi
done
print_success "$hardened service(s) systemd durci(s)"
mark_step_done "$step_name"
}
configure_advanced_pam() {
local step_name="configure_advanced_pam"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Configuration PAM avancée"
backup_file "/etc/pam.d/common-password"
grep -q "pam_unix.so" /etc/pam.d/common-password && {
grep -q "sha512" /etc/pam.d/common-password || \
sed -i 's/pam_unix.so.*/& sha512/' /etc/pam.d/common-password
grep -q "rounds=" /etc/pam.d/common-password || \
sed -i 's/pam_unix.so.*/& rounds=500000/' /etc/pam.d/common-password
}
print_info "Application expiration mots de passe aux utilisateurs..."
while IFS=: read -r user _ uid _ _ _ shell; do
[[ "$uid" -ge 1000 || "$user" == "root" ]] && \
[[ -n "$shell" ]] && \
[[ "$shell" != *"nologin"* ]] && \
[[ "$shell" != *"false"* ]] && {
chage -M 90 "$user" 2>/dev/null || true
chage -d 0 "$user" 2>/dev/null || true
print_info "$user: Expiration 90 jours configurée"
}
done < /etc/passwd
print_success "Configuration PAM avancée appliquée"
mark_step_done "$step_name"
}
check_partition_layout() {
local step_name="check_partition_layout"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Vérification disposition partitions"
local partitions=(/home /tmp /var /var/log /var/log/audit)
local warnings=()
for partition in "${partitions[@]}"; do
mount | grep -q " on ${partition} " && {
local device=$(mount | grep " on ${partition} " | awk '{print $1}')
print_info "$partition: Partition séparée ($device)"
} || {
warnings+=("$partition")
print_warning "$partition: Non monté séparément"
}
done
[[ ${#warnings[@]} -eq 0 ]] && \
print_success "Toutes les partitions critiques séparées" || {
print_warning "${#warnings[@]} partition(s) non séparée(s)"
detect_container && \
print_info "Conteneur: partitions gérées par l'hôte"
}
mark_step_done "$step_name"
}
check_vmlinuz() {
local step_name="check_vmlinuz"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Vérification fichiers noyau"
local files=(/vmlinuz /boot/vmlinuz "/boot/vmlinuz-$(uname -r)" "/boot/initrd.img-$(uname -r)")
local found=0
for kfile in "${files[@]}"; do
[[ -f "$kfile" ]] && {
found=$((found + 1))
print_info "$kfile ($(du -h "$kfile" | cut -f1))"
}
done
[[ $found -eq 0 ]] && {
print_warning "Aucun fichier noyau trouvé"
detect_container && \
print_info "Conteneur: noyau géré par l'hôte" || {
print_error "Système physique: fichiers noyau manquants!"
apt-get install --reinstall "linux-image-$(uname -r)" 2>/dev/null || \
print_warning "Échec réinstallation noyau"
}
} || print_success "$found fichier(s) noyau trouvé(s)"
mark_step_done "$step_name"
}
run_chkrootkit() {
local step_name="run_chkrootkit"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Exécution de chkrootkit"
print_info "Scan rootkit en cours..."
chkrootkit > "$BACKUP_DIR/chkrootkit_report.log" 2>&1 || \
print_warning "Chkrootkit a détecté des avertissements"
print_success "Scan chkrootkit terminé"
mark_step_done "$step_name"
}
prepare_ssh_cleanup() {
local step_name="prepare_ssh_cleanup"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
print_step "Préparation nettoyage port SSH 22"
local ssh_port=$(get_ssh_port_to_use)
if [[ "$ssh_port" == "22022" ]]; then
print_warning "Port SSH 22 toujours actif (temporaire)"
print_info "Après avoir testé SSH sur le port $ssh_port:"
print_info "Exécutez: $0 --cleanup-ssh"
else
print_info "Conteneur LXC - Port SSH principal: 22"
print_info "Aucun nettoyage nécessaire"
fi
mark_step_done "$step_name"
}
run_lynis_audit() {
local step_name="run_lynis_audit"
if check_step_done "$step_name"; then
skip_step "${STEP_DESCRIPTIONS[$step_name]}"
return 0
fi
if [[ "$AUTO_SKIP_LYNIS" == "yes" ]]; then
print_info "Audit Lynis ignoré (AUTO_SKIP_LYNIS=yes)"
mark_step_done "$step_name"
return 0
fi
print_step "Exécution de l'audit Lynis"
print_info "Audit système en cours..."
lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1
# Extraire le score
local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1)
local max_score=100
[[ -z "$score" ]] && score=0
# Afficher le score
local percentage=$((score * 100 / max_score))
local bar_length=50
local filled_length=$((percentage * bar_length / 100))
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " SCORE LYNIS FINAL"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -n "Score: ["
for ((i=0; i<filled_length; i++)); do echo -n "█"; done
for ((i=filled_length; i<bar_length; i++)); do echo -n "░"; done
echo "] $score/$max_score"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_success "Audit Lynis terminé - Rapport: $SECURITY_REPORT"
mark_step_done "$step_name"
}
# ==============================================================================
# FONCTION NETTOYAGE SSH AUTOMATIQUE
# ==============================================================================
cleanup_ssh_port() {
print_step "Nettoyage définitif port SSH 22"
local ssh_port=$(get_ssh_port_to_use)
if [[ "$ssh_port" == "22" ]]; then
print_info "Port SSH principal déjà sur 22 - aucun nettoyage nécessaire"
return 0
fi
if auto_confirm || [[ "$AUTO_CLEANUP_SSH" == "yes" ]]; then
print_info "Nettoyage automatique du port 22"
# Supprimer le port 22 de la configuration SSH
backup_file "/etc/ssh/sshd_config"
sed -i '/^Port 22$/d' /etc/ssh/sshd_config
# Supprimer la règle UFW pour le port 22
if command -v ufw > /dev/null 2>&1 && ufw status | grep -q "22/tcp"; then
ufw delete allow 22/tcp 2>/dev/null || true
fi
# Redémarrer SSH
if sshd -t && systemctl restart sshd 2>/dev/null; then
print_success "Port SSH 22 supprimé - SSH disponible uniquement sur le port $ssh_port"
else
print_error "Erreur configuration - restauration..."
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null
return 1
fi
else
print_warning "Nettoyage SSH non effectué (utilisez --cleanup-ssh ou AUTO_CLEANUP_SSH=yes)"
print_info "Ports SSH actifs: 22 (temporaire) et $ssh_port (permanent)"
fi
}
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"
# Vérifier si unattended-upgrades est installé
if ! dpkg -l | grep -q "^ii.*unattended-upgrades"; then
print_info "Installation de unattended-upgrades..."
DEBIAN_FRONTEND=noninteractive apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq unattended-upgrades apt-listchanges needrestart 2>/dev/null || {
print_warning "Échec installation complète - continuation avec ce qui est disponible"
}
fi
# Configuration principale de unattended-upgrades
backup_file "/etc/apt/apt.conf.d/50unattended-upgrades"
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
// Configuration des mises à jour automatiques sécurisées
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}:${distro_codename}-updates";
"${distro_id}ESM:${distro_codename}";
"${distro_id}ESM-Apps:${distro_codename}";
};
// Options de comportement
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
// Nettoyage automatique
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
// Notifications par email (si postfix/sendmail est installé)
Unattended-Upgrade::Mail "root";
Unattended-Upgrade::MailOnlyOnError "true";
Unattended-Upgrade::MailReport "on-change";
// Téléchargement et installation
Unattended-Upgrade::Download-Upgradeable-Packages "true";
Unattended-Upgrade::DevRelease "auto";
// Mise à jour du GRUB si nécessaire
Unattended-Upgrade::Update-Boot-App "true";
EOF
# Configuration périodique pour APT (ne pas essayer de sauvegarder si le fichier n'existe pas)
if [[ -f "/etc/apt/apt.conf.d/10periodic" ]]; then
backup_file "/etc/apt/apt.conf.d/10periodic"
fi
cat > /etc/apt/apt.conf.d/10periodic << 'EOF'
// Planification des tâches APT automatiques
APT::Periodic::Update-Package-Lists "1"; // Mettre à jour les listes quotidiennement
APT::Periodic::Download-Upgradeable-Packages "1"; // Télécharger les paquets upgradables
APT::Periodic::AutocleanInterval "7"; // Nettoyer le cache tous les 7 jours
APT::Periodic::Unattended-Upgrade "1"; // Exécuter unattended-upgrade
APT::Periodic::Verbose "0"; // Mode silencieux
APT::Periodic::RandomSleep "300"; // Délai aléatoire pour éviter la charge simultanée
EOF
# Configuration pour apt-listchanges (uniquement si le fichier existe)
if [[ -f "/etc/apt/listchanges.conf" ]]; then
backup_file "/etc/apt/listchanges.conf"
sed -i 's/^frontend=.*/frontend=mail/' /etc/apt/listchanges.conf 2>/dev/null || true
sed -i 's/^which=.*/which=both/' /etc/apt/listchanges.conf 2>/dev/null || true
sed -i 's/^email_address=.*/email_address=root/' /etc/apt/listchanges.conf 2>/dev/null || true
sed -i 's/^confirm=.*/confirm=0/' /etc/apt/listchanges.conf 2>/dev/null || true
else
# Créer une configuration minimale si le fichier n'existe pas
cat > /etc/apt/listchanges.conf << 'EOF' 2>/dev/null || true
[apt]
frontend=mail
email_address=root
confirm=0
save_seen=/var/lib/apt/listchanges.db
which=both
EOF
fi
# Configuration pour needrestart (gestion conditionnelle)
if [[ -f "/etc/needrestart/needrestart.conf" ]]; then
backup_file "/etc/needrestart/needrestart.conf"
sed -i 's/^#\$nrconf{restart}.*/\$nrconf{restart} = '"'a'"';/' /etc/needrestart/needrestart.conf 2>/dev/null || true
sed -i 's/^#\$nrconf{override_rc}.*/\$nrconf{override_rc} = '"'0'"';/' /etc/needrestart/needrestart.conf 2>/dev/null || true
else
# Créer le répertoire et le fichier si nécessaire
mkdir -p /etc/needrestart/ 2>/dev/null || true
cat > /etc/needrestart/needrestart.conf << 'EOF' 2>/dev/null || true
# Configuration de needrestart
$nrconf{restart} = 'a'; # Redémarrer automatiquement les services
$nrconf{override_rc} = 0; # Ne pas modifier les scripts rc
$nrconf{html_browser} = ''; # Pas de navigateur HTML
EOF
fi
# Configuration du timer systemd (optionnel)
if command -v systemctl >/dev/null 2>&1; then
# Créer le répertoire pour les overrides
mkdir -p /etc/systemd/system/apt-daily.timer.d/ 2>/dev/null || true
# Ne pas essayer de sauvegarder un fichier qui n'existe pas
if [[ -f "/etc/systemd/system/apt-daily.timer" ]]; then
backup_file "/etc/systemd/system/apt-daily.timer"
fi
# Créer l'override
cat > /etc/systemd/system/apt-daily.timer.d/override.conf << 'EOF' 2>/dev/null || true
[Timer]
OnCalendar=
OnCalendar=*-*-* 3:00
RandomizedDelaySec=1h
EOF
systemctl daemon-reload 2>/dev/null || true
fi
# Activer le service
print_info "Activation du service unattended-upgrades..."
if systemctl is-enabled unattended-upgrades >/dev/null 2>&1; then
systemctl restart unattended-upgrades 2>&1 | tee -a "$LOG_FILE" || true
print_info "Service unattended-upgrades redémarré"
else
systemctl enable unattended-upgrades 2>&1 | tee -a "$LOG_FILE" || true
systemctl start unattended-upgrades 2>&1 | tee -a "$LOG_FILE" || true
print_info "Service unattended-upgrades activé et démarré"
fi
# Vérification simple
sleep 2
if systemctl is-active unattended-upgrades >/dev/null 2>&1; then
print_success "Service unattended-upgrades actif"
else
print_warning "Service unattended-upgrades inactif (démarrage différé)"
fi
# Tester la configuration (optionnel)
if command -v unattended-upgrade >/dev/null 2>&1; then
print_info "Test de configuration unattended-upgrades..."
unattended-upgrade --dry-run --debug 2>&1 | grep -i "checking\|result" | head -5 | tee -a "$LOG_FILE" || true
fi
# Afficher un résumé
echo ""
print_info "Configuration des mises à jour automatiques:"
echo " ✓ Configuration unattended-upgrades appliquée"
echo " ✓ Planification quotidienne configurée"
echo " ✓ Mises à jour de sécurité activées"
echo " ✓ Nettoyage automatique activé"
echo ""
print_success "Mises à jour automatiques configurées"
mark_step_done "$step_name"
}
# ==============================================================================
# FONCTIONS PRINCIPALES
# ==============================================================================
check_requirements() {
print_info "Vérification des prérequis..."
[[ $EUID -ne 0 ]] && {
print_error "Ce script doit être exécuté en tant que root"
exit 1
}
[[ ! -f /etc/debian_version ]] && {
print_warning "Système non Debian/Ubuntu - compatibilité limitée"
if ! auto_confirm; then
read -p "Continuer malgré tout ? (o/N): " -r confirm
[[ "$confirm" != "o" ]] && exit 1
fi
}
mkdir -p "$BACKUP_DIR"
touch "$LOG_FILE" "$STATUS_FILE" 2>/dev/null || {
print_error "Impossible de créer les fichiers de log"
exit 1
}
print_success "Prérequis vérifiés"
}
print_header() {
clear
echo -e "${CYAN}"
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ SCRIPT DE DURCISSEMENT SYSTÈME OPTIMISÉ v8.2 ║"
echo "║ Sécurité Debian/Ubuntu ║"
echo "║ MODE AUTONOME ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo -e "${BLUE}Informations système:${NC}"
echo " • Distribution: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"')"
echo " • Noyau: $(uname -r)"
echo " • Hostname: $(hostname)"
echo " • Type: $(detect_container && echo "Conteneur/LXC" || echo "Hôte physique/VM")"
echo ""
echo -e "${BLUE}Configuration automatique:${NC}"
echo " • Port SSH: $AUTO_SSH_PORT"
echo " • Fuseau horaire: $AUTO_TIMEZONE"
echo " • Umask: $AUTO_UMASK"
echo " • Durée max mot de passe: $AUTO_PASS_MAX_DAYS jours"
echo " • Mode autonome: $( [[ "$UNATTENDED" == true ]] && echo "Activé" || echo "Désactivé" )"
echo ""
echo -e "${YELLOW}Configuration des composants:${NC}"
echo " • Fail2ban: $( [[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && echo "✓" || echo "✗" )"
echo " • UFW: $( [[ "$AUTO_ENABLE_UFW" == "yes" ]] && echo "✓" || echo "✗" )"
echo " • AIDE: $( [[ "$AUTO_ENABLE_AIDE" == "yes" ]] && echo "✓" || echo "✗" )"
echo " • ClamAV: $( [[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && echo "✓" || echo "✗" )"
echo ""
if ! auto_confirm; then
echo -e "${YELLOW}⚠ Ce script va modifier profondément la configuration système ⚠${NC}"
echo ""
read -p "Appuyez sur Entrée pour continuer ou Ctrl+C pour annuler..."
else
echo -e "${GREEN}Mode autonome activé - Démarrage automatique dans 3 secondes...${NC}"
sleep 3
fi
}
print_summary() {
echo -e "\n${GREEN}"
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ DURCISSEMENT SYSTÈME TERMINÉ AVEC SUCCÈS ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
local ssh_port=$(get_ssh_port_to_use)
echo -e "\n${MAGENTA}════════════════════ RÉSUMÉ FINAL ════════════════════${NC}\n"
echo -e "${YELLOW}🔐 CONFIGURATIONS APPLIQUÉES:${NC}"
echo " • Mot de passe root: $( [[ "$AUTO_CHANGE_ROOT_PWD" == "yes" ]] && echo "Changé aléatoirement" || echo "Non modifié" )"
echo " • SSH: Port $ssh_port, authentification par clé uniquement"
echo " • Pare-feu: $( [[ "$AUTO_ENABLE_UFW" == "yes" ]] && echo "UFW activé" || echo "UFW désactivé" )"
echo " • Fail2ban: $( [[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && echo "Activé" || echo "Désactivé" )"
echo " • AIDE: $( [[ "$AUTO_ENABLE_AIDE" == "yes" ]] && echo "Base initialisée + vérif. quotidienne" || echo "Désactivé" )"
echo " • Mises à jour automatiques: Configurées"
echo " • Politique mot de passe: 14 caractères minimum, 3 classes"
echo ""
if [[ -f "$SECURITY_REPORT" ]]; then
local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1)
[[ -n "$score" ]] && {
echo -e "${YELLOW}📊 SCORE DE SÉCURITÉ:${NC}"
echo " • Score Lynis: $score/100"
echo ""
}
fi
echo -e "${YELLOW}📁 FICHIERS GÉNÉRÉS:${NC}"
echo " • Log complet: $LOG_FILE"
echo " • Sauvegardes: $BACKUP_DIR"
echo " • Rapport Lynis: $SECURITY_REPORT"
echo ""
echo -e "${YELLOW}🔍 COMMANDES UTILES:${NC}"
echo " 1. Test SSH: ssh -p $ssh_port $(whoami)@localhost"
echo " 2. Statut UFW: ufw status verbose"
echo " 3. Statut Fail2ban: fail2ban-client status"
echo " 4. Vérification AIDE: aide --check"
echo ""
if [[ "$ssh_port" != "22" ]] && [[ "$AUTO_CLEANUP_SSH" != "yes" ]]; then
echo -e "${RED}⚠ ACTION REQUISE:${NC}"
echo " Le port SSH 22 est encore actif temporairement."
echo " Après avoir testé le port $ssh_port, exécutez:"
echo " $0 --cleanup-ssh"
echo " Ou redémarrez avec: AUTO_CLEANUP_SSH=yes $0 --unattended"
echo ""
fi
echo -e "${GREEN}✅ Le système est maintenant durci et sécurisé.${NC}"
}
main() {
parse_arguments "$@"
if $LIST_STEPS; then
list_all_steps
exit 0
fi
if $SHOW_STATUS; then
show_step_status
exit 0
fi
if [[ "$AUTO_CLEANUP_SSH" == "yes" ]] && [[ $# -eq 0 ]]; then
cleanup_ssh_port
exit 0
fi
check_requirements
print_header
log_message "==================================================" "START"
log_message "Démarrage durcissement système v8.2" "START"
log_message "Mode: $( [[ "$UNATTENDED" == true ]] && echo "Autonome" || echo "Interactif" )" "START"
log_message "Port SSH: $AUTO_SSH_PORT" "START"
log_message "Hostname: $(hostname)" "START"
log_message "Changement mdp root: $AUTO_CHANGE_ROOT_PWD" "START"
log_message "==================================================" "START"
# Exécution des étapes
install_security_tools
change_root_password
detect_open_ports
configure_process_accounting
configure_sysctl_security
configure_log_permissions
configure_pam_password_policy
configure_login_defs
configure_umask
configure_aide_sha512
initialize_aide_db
[[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && configure_clamav
configure_chrony
harden_ssh
configure_banners
configure_firewall_ports
configure_fail2ban
remove_unneeded_packages
restrict_file_permissions
disable_risky_kernel_modules
configure_security_limits
verify_packages_integrity
configure_automatic_updates
configure_aide_cron
harden_smtp_banner
harden_systemd_services
configure_advanced_pam
check_partition_layout
check_vmlinuz
run_chkrootkit
prepare_ssh_cleanup
run_lynis_audit
# Nettoyage SSH si demandé
if [[ "$AUTO_CLEANUP_SSH" == "yes" ]] || auto_confirm; then
cleanup_ssh_port
fi
print_summary
log_message "Durcissement terminé avec succès" "END"
log_message "Port SSH final: $(get_ssh_port_to_use)" "END"
log_message "==================================================" "END"
}
# ==============================================================================
# EXÉCUTION PRINCIPALE
# ==============================================================================
trap 'print_error "Script interrompu"; exit 130' INT TERM
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"