#!/bin/bash # Script de durcissement système Linux # Version: 3.0 - Idempotent et Robuste # Auteur: Optimisé pour sécurité renforcée et réutilisabilité set -euo pipefail # ============================================================================ # CONFIGURATION GLOBALE # ============================================================================ readonly SCRIPT_VERSION="3.0" readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly STATE_DIR="/var/lib/hardening-script" readonly BACKUP_BASE_DIR="/root/backup_hardening" readonly LOG_FILE="/var/log/hardening-script.log" readonly SSH_PORT="${SSH_PORT:-2222}" readonly LOCK_FILE="/var/run/hardening-script.lock" # Couleurs readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly MAGENTA='\033[0;35m' readonly CYAN='\033[0;36m' readonly NC='\033[0m' # ============================================================================ # FONCTIONS UTILITAIRES # ============================================================================ # Logging amélioré avec fichier log() { local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1" echo -e "${GREEN}${msg}${NC}" echo "$msg" >> "$LOG_FILE" } warn() { local msg="[ATTENTION] $1" echo -e "${YELLOW}${msg}${NC}" echo "$msg" >> "$LOG_FILE" } error() { local msg="[ERREUR] $1" echo -e "${RED}${msg}${NC}" >&2 echo "$msg" >> "$LOG_FILE" } info() { local msg="[INFO] $1" echo -e "${CYAN}${msg}${NC}" echo "$msg" >> "$LOG_FILE" } success() { local msg="[OK] $1" echo -e "${GREEN}${msg}${NC}" echo "$msg" >> "$LOG_FILE" } # Fonction pour vérifier si une tâche a déjà été exécutée (idempotence) is_task_done() { local task_name="$1" [[ -f "${STATE_DIR}/${task_name}.done" ]] } # Marquer une tâche comme terminée mark_task_done() { local task_name="$1" mkdir -p "$STATE_DIR" touch "${STATE_DIR}/${task_name}.done" echo "$(date '+%Y-%m-%d %H:%M:%S')" > "${STATE_DIR}/${task_name}.timestamp" } # Réinitialiser l'état d'une tâche reset_task() { local task_name="$1" rm -f "${STATE_DIR}/${task_name}.done" "${STATE_DIR}/${task_name}.timestamp" } # Exécuter une tâche de manière idempotente run_task() { local task_name="$1" local task_function="$2" local force="${3:-false}" if is_task_done "$task_name" && [[ "$force" != "true" ]]; then info "Tâche '$task_name' déjà effectuée - ignorée (utilisez --force pour réexécuter)" return 0 fi log "Exécution de la tâche: $task_name" if $task_function; then mark_task_done "$task_name" success "Tâche '$task_name' terminée avec succès" return 0 else error "Échec de la tâche '$task_name'" return 1 fi } # Gestion du verrou pour éviter les exécutions simultanées acquire_lock() { exec 200>"$LOCK_FILE" if ! flock -n 200; then error "Une autre instance du script est en cours d'exécution" exit 1 fi } release_lock() { flock -u 200 rm -f "$LOCK_FILE" } # Nettoyage amélioré cleanup() { error "Script interrompu. Nettoyage en cours..." release_lock exit 1 } trap cleanup INT TERM EXIT # ============================================================================ # VÉRIFICATIONS PRÉLIMINAIRES # ============================================================================ check_root() { if [[ $EUID -ne 0 ]]; then error "Ce script doit être exécuté en tant que root (sudo ./script.sh)" exit 1 fi } check_distribution() { if [[ -f /etc/os-release ]]; then source /etc/os-release info "Distribution détectée: $PRETTY_NAME" if [[ "$ID_LIKE" =~ (debian|ubuntu) ]] || [[ "$ID" =~ (debian|ubuntu) ]]; then return 0 fi fi error "Ce script est conçu pour les distributions basées sur Debian/Ubuntu" exit 1 } check_disk_space() { local required_space=1048576 # 1GB en KB local available_space=$(df / | awk 'NR==2 {print $4}') if [[ $available_space -lt $required_space ]]; then error "Espace disque insuffisant. Requis: 1GB, Disponible: $((available_space/1024))MB" exit 1 fi } check_internet_connectivity() { if ! ping -c 1 -W 2 8.8.8.8 &> /dev/null; then warn "Pas de connectivité Internet détectée. Certaines opérations peuvent échouer." read -p "Continuer quand même? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi fi } # ============================================================================ # SAUVEGARDE ET RESTAURATION # ============================================================================ backup_configs() { local backup_dir="${BACKUP_BASE_DIR}_$(date +%Y%m%d_%H%M%S)" if is_task_done "backup_configs"; then info "Sauvegarde déjà effectuée" return 0 fi log "Sauvegarde des fichiers de configuration..." mkdir -p "$backup_dir" local files=( "/etc/ssh/sshd_config" "/etc/login.defs" "/etc/pam.d/common-password" "/etc/pam.d/common-auth" "/etc/security/limits.conf" "/etc/sysctl.conf" "/etc/fstab" "/etc/default/grub" "/etc/audit/auditd.conf" "/etc/rsyslog.conf" ) for file in "${files[@]}"; do if [[ -f "$file" ]]; then cp -p "$file" "$backup_dir/" 2>/dev/null || warn "Impossible de sauvegarder $file" fi done # Sauvegarder la liste des paquets installés dpkg --get-selections > "$backup_dir/installed_packages.list" # Sauvegarder les règles UFW if command -v ufw &> /dev/null; then ufw status numbered > "$backup_dir/ufw_rules.txt" 2>/dev/null || true fi # Sauvegarder les services actifs systemctl list-units --type=service --state=running > "$backup_dir/active_services.txt" # Créer un fichier de métadonnées cat > "$backup_dir/metadata.txt" < "${STATE_DIR}/last_backup_dir" } # ============================================================================ # MISE À JOUR ET INSTALLATION # ============================================================================ update_system() { log "Mise à jour du système..." export DEBIAN_FRONTEND=noninteractive # Nettoyer les verrous APT si nécessaire rm -f /var/lib/apt/lists/lock rm -f /var/cache/apt/archives/lock rm -f /var/lib/dpkg/lock* # Configurer dpkg pour éviter les prompts dpkg --configure -a apt update -qq || { error "Échec de la mise à jour des dépôts" return 1 } apt upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" apt autoremove -y -qq apt autoclean -qq # Mettre à jour la base de données locate si présente command -v updatedb &> /dev/null && updatedb || true } install_security_tools() { log "Installation des outils de durcissement..." local packages=( # Outils de sécurité lynis fail2ban ufw aide aide-common # Antivirus clamav clamav-daemon clamav-freshclam # Détection rootkit chkrootkit rkhunter # Audit et logging auditd audispd-plugins rsyslog logwatch logrotate # Authentification libpam-tmpdir libpam-pwquality libpam-cracklib # Monitoring sysstat iotop htop # Outils réseau net-tools tcpdump nmap # Synchronisation temps chrony # Détection d'intrusion psad # Autres debsums apt-listchanges unattended-upgrades acct apparmor apparmor-utils apparmor-profiles tiger needrestart debsecan ) local failed_packages=() export DEBIAN_FRONTEND=noninteractive for package in "${packages[@]}"; do if ! dpkg -l | grep -q "^ii $package "; then if apt install -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" "$package" 2>/dev/null; then info "✓ $package installé" else warn "✗ Échec de l'installation de $package" failed_packages+=("$package") fi else info "✓ $package déjà installé" fi done if [[ ${#failed_packages[@]} -gt 0 ]]; then warn "Paquets non installés: ${failed_packages[*]}" fi } # ============================================================================ # CONFIGURATION FAIL2BAN # ============================================================================ configure_fail2ban() { log "Configuration de Fail2ban..." # Configuration locale qui ne sera pas écrasée cat > /etc/fail2ban/jail.local </dev/null; then cat >> /etc/fail2ban/jail.local </dev/null; then cat >> /etc/fail2ban/jail.local </dev/null; then cat >> /etc/fail2ban/jail.local < /dev/null; then systemctl enable fail2ban systemctl restart fail2ban success "Fail2ban configuré et démarré" else error "Erreur dans la configuration Fail2ban" return 1 fi } # ============================================================================ # CONFIGURATION UFW (PARE-FEU) # ============================================================================ configure_ufw() { log "Configuration du pare-feu UFW..." # Sauvegarder les règles actuelles si UFW est déjà actif if ufw status | grep -q "Status: active"; then ufw status numbered > "${STATE_DIR}/ufw_backup_$(date +%Y%m%d_%H%M%S).txt" fi # Désactiver UFW temporairement ufw --force disable # Réinitialiser SEULEMENT si pas déjà configuré if [[ ! -f "${STATE_DIR}/ufw_configured" ]]; then ufw --force reset fi # Politique par défaut ufw default deny incoming ufw default allow outgoing ufw default deny forward # Autoriser le loopback ufw allow in on lo ufw allow out on lo # SSH sur port personnalisé ufw allow ${SSH_PORT}/tcp comment 'SSH Custom Port' # Services web (commentés par défaut) # ufw allow 80/tcp comment 'HTTP' # ufw allow 443/tcp comment 'HTTPS' # DNS (sortant seulement, entrant généralement pas nécessaire) ufw allow out 53 comment 'DNS' # NTP ufw allow out 123/udp comment 'NTP' # Protection contre les scans de ports ufw limit ${SSH_PORT}/tcp # Logging ufw logging medium # Activer UFW ufw --force enable touch "${STATE_DIR}/ufw_configured" # Afficher les règles ufw status verbose } # ============================================================================ # CONFIGURATION SSH SÉCURISÉE # ============================================================================ configure_ssh() { log "Configuration sécurisée de SSH..." # Vérifier si des clés SSH existent pour les utilisateurs local has_ssh_keys=false for user_home in /home/*; do if [[ -f "$user_home/.ssh/authorized_keys" ]] && [[ -s "$user_home/.ssh/authorized_keys" ]]; then has_ssh_keys=true break fi done if [[ "$has_ssh_keys" == "false" ]]; then warn "ATTENTION: Aucune clé SSH trouvée pour les utilisateurs !" warn "L'authentification par mot de passe SSH sera DÉSACTIVÉE." read -p "Continuer? Vous pourriez perdre l'accès SSH! (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then error "Configuration SSH annulée. Configurez d'abord vos clés SSH." return 1 fi fi # Sauvegarder la config actuelle si pas déjà fait [[ ! -f /etc/ssh/sshd_config.bak ]] && cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak # Configuration SSH durcie cat > /etc/ssh/sshd_config < /etc/issue.net <<'EOF' ################################################################################ # # # SYSTÈME SÉCURISÉ - ACCÈS RESTREINT # # # # Ce système est réservé exclusivement aux utilisateurs autorisés. # # Toutes les connexions et activités sont surveillées et enregistrées. # # L'accès non autorisé est strictement interdit et sera poursuivi. # # # # Si vous n'êtes pas autorisé, déconnectez-vous immédiatement. # # # ################################################################################ EOF # Appliquer aussi la bannière au login local cp /etc/issue.net /etc/issue # Vérifier et appliquer la configuration if sshd -t 2>/dev/null; then systemctl restart sshd success "Configuration SSH appliquée - Port: ${SSH_PORT}" info "Testez votre connexion SSH sur le port ${SSH_PORT} AVANT de fermer cette session!" else error "Erreur dans la configuration SSH!" sshd -t return 1 fi } # ============================================================================ # CONFIGURATION SYSCTL (KERNEL) # ============================================================================ configure_sysctl() { log "Configuration des paramètres kernel (sysctl)..." # Créer un fichier de configuration dédié cat > /etc/sysctl.d/99-hardening.conf <<'EOF' # Configuration de sécurité kernel - Hardening Script # Généré automatiquement # ============================================================================ # SÉCURITÉ RÉSEAU IPv4 # ============================================================================ # Protection contre IP spoofing (vérification de la source) net.ipv4.conf.default.rp_filter = 1 net.ipv4.conf.all.rp_filter = 1 # Ignorer les pings ICMP (protection contre ping flood) net.ipv4.icmp_echo_ignore_all = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 # Ignorer les redirections ICMP net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 # Ne pas envoyer de redirections ICMP net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # Ignorer les paquets source routed net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 # Protection SYN flood net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 5 # Désactiver le routage IP si non routeur net.ipv4.ip_forward = 0 net.ipv4.conf.all.forwarding = 0 net.ipv4.conf.default.forwarding = 0 # Log des paquets suspects net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 # Protection contre les attaques de temps net.ipv4.tcp_timestamps = 0 # Amélioration des performances TCP net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_sack = 1 # ============================================================================ # SÉCURITÉ RÉSEAU IPv6 # ============================================================================ # Désactiver IPv6 si non utilisé (décommenter si nécessaire) # net.ipv6.conf.all.disable_ipv6 = 1 # net.ipv6.conf.default.disable_ipv6 = 1 # net.ipv6.conf.lo.disable_ipv6 = 1 # Si IPv6 est utilisé, appliquer les mêmes protections net.ipv6.conf.all.accept_redirects = 0 net.ipv6.conf.default.accept_redirects = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv6.conf.default.accept_source_route = 0 net.ipv6.conf.all.accept_ra = 0 net.ipv6.conf.default.accept_ra = 0 net.ipv6.conf.all.forwarding = 0 # ============================================================================ # SÉCURITÉ KERNEL # ============================================================================ # Restreindre l'accès aux logs kernel kernel.dmesg_restrict = 1 # Masquer les adresses kernel kernel.kptr_restrict = 2 # Protection ptrace (empêche l'attachement aux processus) kernel.yama.ptrace_scope = 1 # Protection contre les attaques par lien symbolique fs.protected_symlinks = 1 fs.protected_hardlinks = 1 # Protection FIFO et fichiers réguliers fs.protected_fifos = 2 fs.protected_regular = 2 # Désactiver les SysRq sauf reboot/shutdown d'urgence kernel.sysrq = 16 # Limiter la visibilité des processus # kernel.unprivileged_userns_clone = 0 # ============================================================================ # AUTRES PARAMÈTRES DE SÉCURITÉ # ============================================================================ # Randomisation de l'espace d'adressage kernel.randomize_va_space = 2 # Core dumps kernel.core_uses_pid = 1 fs.suid_dumpable = 0 # Limite des fichiers ouverts fs.file-max = 2097152 # IPC kernel.msgmnb = 65536 kernel.msgmax = 65536 # Shared memory kernel.shmmax = 68719476736 kernel.shmall = 4294967296 EOF # Appliquer immédiatement les changements sysctl -p /etc/sysctl.d/99-hardening.conf success "Paramètres kernel appliqués" } # ============================================================================ # POLITIQUE DE MOTS DE PASSE # ============================================================================ configure_password_policy() { log "Configuration de la politique de mots de passe..." # Sauvegarde [[ ! -f /etc/login.defs.bak ]] && cp /etc/login.defs /etc/login.defs.bak # Configuration login.defs (idempotent avec sed) sed -i.tmp 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs sed -i.tmp 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' /etc/login.defs sed -i.tmp 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 14/' /etc/login.defs sed -i.tmp 's/^UMASK.*/UMASK 027/' /etc/login.defs sed -i.tmp 's/^ENCRYPT_METHOD.*/ENCRYPT_METHOD SHA512/' /etc/login.defs # Ajouter les paramètres manquants (idempotent) grep -q "^SHA_CRYPT_MIN_ROUNDS" /etc/login.defs || echo "SHA_CRYPT_MIN_ROUNDS 5000" >> /etc/login.defs grep -q "^SHA_CRYPT_MAX_ROUNDS" /etc/login.defs || echo "SHA_CRYPT_MAX_ROUNDS 5000" >> /etc/login.defs grep -q "^FAILLOG_ENAB" /etc/login.defs || echo "FAILLOG_ENAB yes" >> /etc/login.defs grep -q "^LOG_UNKFAIL_ENAB" /etc/login.defs || echo "LOG_UNKFAIL_ENAB yes" >> /etc/login.defs # Configuration PAM pour la qualité des mots de passe (idempotent) if [[ -f /etc/pam.d/common-password ]]; then [[ ! -f /etc/pam.d/common-password.bak ]] && cp /etc/pam.d/common-password /etc/pam.d/common-password.bak if ! grep -q "pam_pwquality.so" /etc/pam.d/common-password; then sed -i '/pam_unix.so/i password requisite pam_pwquality.so retry=3 minlen=14 difok=4 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 maxrepeat=2 reject_username enforce_for_root' /etc/pam.d/common-password fi # Ajouter pam_pwhistory pour empêcher la réutilisation des mots de passe if ! grep -q "pam_pwhistory.so" /etc/pam.d/common-password; then sed -i '/pam_unix.so/a password required pam_pwhistory.so remember=5 use_authtok' /etc/pam.d/common-password fi fi # Configuration de verrouillage de compte après échecs if [[ -f /etc/pam.d/common-auth ]]; then [[ ! -f /etc/pam.d/common-auth.bak ]] && cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak if ! grep -q "pam_faillock.so" /etc/pam.d/common-auth; then sed -i '1i auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900' /etc/pam.d/common-auth echo "auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900" >> /etc/pam.d/common-auth echo "auth sufficient pam_faillock.so authsucc" >> /etc/pam.d/common-auth fi fi success "Politique de mots de passe configurée" } # ============================================================================ # LIMITES SYSTÈME # ============================================================================ configure_limits() { log "Configuration des limites système..." [[ ! -f /etc/security/limits.conf.bak ]] && cp /etc/security/limits.conf /etc/security/limits.conf.bak # Ajouter les limites si elles n'existent pas déjà if ! grep -q "# Hardening Script Limits" /etc/security/limits.conf; then cat >> /etc/security/limits.conf <<'EOF' # Hardening Script Limits # Core dumps désactivés pour tous * hard core 0 * soft core 0 # Limites de processus * soft nproc 65536 * hard nproc 65536 # Limites de fichiers ouverts * soft nofile 65536 * hard nofile 65536 # Stack size * hard stack 2048 # Root illimité pour les processus root soft nproc unlimited root hard nproc unlimited EOF fi success "Limites système configurées" } # ============================================================================ # DÉSACTIVATION DES MODULES ET SERVICES # ============================================================================ disable_unnecessary_services() { log "Désactivation des services et modules non nécessaires..." # Désactiver les modules noyau dangereux cat > /etc/modprobe.d/hardening-disable-modules.conf <<'EOF' # Désactivation des protocoles réseau non nécessaires install dccp /bin/true install sctp /bin/true install tipc /bin/true install rds /bin/true # Désactivation des systèmes de fichiers rares install cramfs /bin/true install freevxfs /bin/true install jffs2 /bin/true install hfs /bin/true install hfsplus /bin/true install squashfs /bin/true install udf /bin/true install vfat /bin/true # Désactivation des modules sans fil et bluetooth blacklist firewire-core blacklist thunderbolt blacklist bluetooth blacklist btusb blacklist bnep # Désactivation USB (décommenter si serveur sans USB) # blacklist usb-storage # blacklist uas # Désactivation de modules anciens blacklist floppy EOF # Liste des services à désactiver (si présents) local services_to_disable=( bluetooth.service cups.service cups-browsed.service avahi-daemon.service avahi-daemon.socket rpcbind.service rpcbind.socket nfs-common.service nfs-kernel-server.service iscsid.service open-iscsi.service snapd.service snapd.socket ) for service in "${services_to_disable[@]}"; do if systemctl list-unit-files | grep -q "^${service}"; then if systemctl is-enabled "$service" &>/dev/null; then systemctl disable "$service" 2>/dev/null || true systemctl stop "$service" 2>/dev/null || true info "✓ Service désactivé: $service" fi fi done success "Services inutiles désactivés" } # ============================================================================ # CONFIGURATION ANTIVIRUS (CLAMAV) # ============================================================================ configure_clamav() { log "Configuration de ClamAV..." if ! command -v freshclam &> /dev/null; then warn "ClamAV non installé, passage" return 0 fi # Arrêter le service temporairement systemctl stop clamav-freshclam 2>/dev/null || true # Configuration freshclam pour mises à jour automatiques if [[ -f /etc/clamav/freshclam.conf ]]; then sed -i 's/^#DatabaseMirror/DatabaseMirror/' /etc/clamav/freshclam.conf sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf fi # Mettre à jour la base de données (avec timeout) timeout 300 freshclam || warn "Mise à jour ClamAV timeout (normal lors de la première exécution)" # Activer et démarrer les services systemctl enable clamav-freshclam 2>/dev/null || true systemctl start clamav-freshclam 2>/dev/null || true if systemctl list-unit-files | grep -q "clamav-daemon"; then systemctl enable clamav-daemon 2>/dev/null || true systemctl start clamav-daemon 2>/dev/null || true fi # Créer un script de scan quotidien cat > /etc/cron.daily/clamav-scan <<'EOF' #!/bin/bash SCAN_DIR="/home /var/www" LOG_FILE="/var/log/clamav/daily-scan.log" mkdir -p /var/log/clamav # Scanner les répertoires importants clamscan -r -i --exclude-dir="^/sys" --exclude-dir="^/proc" --exclude-dir="^/dev" \ $SCAN_DIR > "$LOG_FILE" 2>&1 # Envoyer un email si des virus sont détectés if grep -q "Infected files: [1-9]" "$LOG_FILE"; then mail -s "ClamAV: Virus détectés sur $(hostname)" root < "$LOG_FILE" fi EOF chmod +x /etc/cron.daily/clamav-scan success "ClamAV configuré" } # ============================================================================ # CONFIGURATION AIDE (DÉTECTION D'INTRUSION) # ============================================================================ configure_aide() { log "Configuration d'AIDE (détection d'intrusion)..." if ! command -v aide &> /dev/null; then warn "AIDE non installé, passage" return 0 fi # Configuration AIDE personnalisée cat > /etc/aide/aide.conf.d/99_hardening <<'EOF' # Configuration AIDE - Hardening Script # Répertoires système critiques à surveiller /bin R+b+sha256 /sbin R+b+sha256 /usr/bin R+b+sha256 /usr/sbin R+b+sha256 /lib R+b+sha256 /lib64 R+b+sha256 /usr/lib R+b+sha256 /usr/lib64 R+b+sha256 # Configuration système /etc R+b+sha256 !/etc/mtab !/etc/adjtime # Fichiers de démarrage /boot R+b+sha256 # Exclure les répertoires dynamiques !/var/log !/var/cache !/var/tmp !/tmp !/proc !/sys !/dev !/run EOF # Initialiser la base de données AIDE (long processus) if [[ ! -f /var/lib/aide/aide.db ]]; then log "Initialisation de la base AIDE (peut prendre plusieurs minutes)..." aideinit || warn "Échec de l'initialisation AIDE" if [[ -f /var/lib/aide/aide.db.new ]]; then mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db fi fi # Script de vérification quotidienne cat > /etc/cron.daily/aide-check <<'EOF' #!/bin/bash LOG_FILE="/var/log/aide/aide-check-$(date +%Y%m%d).log" mkdir -p /var/log/aide # Exécuter la vérification /usr/bin/aide --check > "$LOG_FILE" 2>&1 EXIT_CODE=$? # Si des changements sont détectés (code 7) if [[ $EXIT_CODE -eq 7 ]]; then # Envoyer un email d'alerte mail -s "AIDE: Modifications détectées sur $(hostname)" root < "$LOG_FILE" # Logger dans syslog logger -t aide-check "ATTENTION: Modifications système détectées" fi # Rotation des logs (garder 30 jours) find /var/log/aide -name "aide-check-*.log" -mtime +30 -delete EOF chmod +x /etc/cron.daily/aide-check success "AIDE configuré" } # ============================================================================ # MISES À JOUR AUTOMATIQUES # ============================================================================ configure_auto_updates() { log "Configuration des mises à jour automatiques de sécurité..." if ! command -v unattended-upgrade &> /dev/null; then warn "unattended-upgrades non installé, passage" return 0 fi # 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}ESMApps:${distro_codename}-apps-security"; "${distro_id}ESM:${distro_codename}-infra-security"; }; Unattended-Upgrade::Package-Blacklist { // Ajouter ici les paquets à ne jamais mettre à jour automatiquement // "mysql-server"; // "postgresql"; }; Unattended-Upgrade::DevRelease "false"; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::MinimalSteps "true"; Unattended-Upgrade::InstallOnShutdown "false"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Automatic-Reboot-WithUsers "false"; Unattended-Upgrade::Automatic-Reboot-Time "03:00"; Unattended-Upgrade::Mail "root"; Unattended-Upgrade::MailReport "on-change"; Unattended-Upgrade::SyslogEnable "true"; Unattended-Upgrade::SyslogFacility "daemon"; EOF cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Verbose "1"; EOF # Tester la configuration unattended-upgrade --dry-run --debug systemctl enable unattended-upgrades systemctl restart unattended-upgrades success "Mises à jour automatiques configurées" } # ============================================================================ # CONFIGURATION AUDITD # ============================================================================ configure_auditd() { log "Configuration d'auditd (audit système)..." if ! command -v auditctl &> /dev/null; then warn "auditd non installé, passage" return 0 fi # Règles d'audit personnalisées cat > /etc/audit/rules.d/hardening.rules <<'EOF' # Règles d'audit - Hardening Script # Supprimer toutes les règles précédentes -D # Buffer -b 8192 # Échec d'audit -f 1 # Surveiller les modifications de configuration -w /etc/passwd -p wa -k identity -w /etc/group -p wa -k identity -w /etc/shadow -p wa -k identity -w /etc/gshadow -p wa -k identity -w /etc/security/opasswd -p wa -k identity # Surveiller les modifications sudo -w /etc/sudoers -p wa -k sudoers -w /etc/sudoers.d/ -p wa -k sudoers # Surveiller les commandes privilégiées -a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged -a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged # Surveiller les modifications du système de fichiers -a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts -a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts # Surveiller les suppressions -a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete -a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete # Surveiller les changements de permissions -a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod -a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod # Surveiller les accès réseau -a always,exit -F arch=b64 -S socket -S connect -k network_connect -a always,exit -F arch=b32 -S socket -S connect -k network_connect # Surveiller les modifications de configuration réseau -w /etc/network/ -p wa -k network_modifications -w /etc/sysconfig/network-scripts/ -p wa -k network_modifications # Surveiller les modifications SSH -w /etc/ssh/sshd_config -p wa -k sshd_config # Surveiller les logs -w /var/log/wtmp -p wa -k logins -w /var/log/btmp -p wa -k logins -w /var/log/lastlog -p wa -k logins # Surveiller les modules kernel -w /sbin/insmod -p x -k modules -w /sbin/rmmod -p x -k modules -w /sbin/modprobe -p x -k modules # Rendre les règles immuables (nécessite un reboot pour changer) -e 2 EOF # Recharger les règles systemctl enable auditd systemctl restart auditd success "Auditd configuré" } # ============================================================================ # CONFIGURATION APPARMOR # ============================================================================ configure_apparmor() { log "Configuration d'AppArmor..." if ! command -v aa-status &> /dev/null; then warn "AppArmor non installé, passage" return 0 fi # Activer AppArmor systemctl enable apparmor systemctl start apparmor # Activer tous les profils en mode enforce aa-enforce /etc/apparmor.d/* 2>/dev/null || true # Afficher le statut aa-status success "AppArmor configuré" } # ============================================================================ # SÉCURISATION DU GRUB # ============================================================================ secure_grub() { log "Sécurisation du bootloader GRUB..." if [[ ! -f /etc/default/grub ]]; then warn "GRUB non trouvé, passage" return 0 fi [[ ! -f /etc/default/grub.bak ]] && cp /etc/default/grub /etc/default/grub.bak # Ajouter les paramètres de sécurité au kernel if ! grep -q "GRUB_CMDLINE_LINUX.*audit=1" /etc/default/grub; then sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="audit=1 /' /etc/default/grub fi # Générer un mot de passe GRUB si demandé read -p "Voulez-vous protéger GRUB par mot de passe? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then log "Génération du mot de passe GRUB..." grub-mkpasswd-pbkdf2 | tee /tmp/grub-password.txt warn "Copiez le hash PBKDF2 et ajoutez-le manuellement dans /etc/grub.d/40_custom" warn "Exemple: set superusers=\"root\"" warn " password_pbkdf2 root " fi # Mettre à jour GRUB update-grub success "GRUB sécurisé" } # ============================================================================ # SÉCURISATION DES PARTITIONS # ============================================================================ secure_partitions() { log "Vérification de la sécurisation des partitions..." # Créer une copie de fstab [[ ! -f /etc/fstab.bak ]] && cp /etc/fstab /etc/fstab.bak # Suggestions pour /tmp, /var/tmp, /dev/shm info "Vérification des options de montage sécurisées..." local suggestions="" # Vérifier /tmp if mount | grep -q "on /tmp "; then if ! mount | grep "on /tmp " | grep -q "noexec"; then suggestions+="- /tmp devrait être monté avec noexec,nodev,nosuid\n" fi else suggestions+="- Envisagez de créer une partition séparée pour /tmp\n" fi # Vérifier /var/tmp if mount | grep -q "on /var/tmp "; then if ! mount | grep "on /var/tmp " | grep -q "noexec"; then suggestions+="- /var/tmp devrait être monté avec noexec,nodev,nosuid\n" fi fi # Vérifier /dev/shm if ! mount | grep "on /dev/shm " | grep -q "noexec"; then suggestions+="- /dev/shm devrait être monté avec noexec,nodev,nosuid\n" fi # Vérifier /home if mount | grep -q "on /home "; then if ! mount | grep "on /home " | grep -q "nodev"; then suggestions+="- /home devrait être monté avec nodev\n" fi fi if [[ -n "$suggestions" ]]; then warn "Suggestions de sécurisation des partitions:" echo -e "$suggestions" warn "Ces modifications doivent être faites manuellement dans /etc/fstab" else success "Partitions correctement sécurisées" fi } # ============================================================================ # CONFIGURATION DES LOGS # ============================================================================ configure_logging() { log "Configuration avancée des logs..." # Configuration rsyslog pour logs centralisés cat > /etc/rsyslog.d/99-hardening.conf <<'EOF' # Configuration rsyslog - Hardening Script # Logger les messages authpriv séparément authpriv.* /var/log/auth.log # Logger les cron jobs cron.* /var/log/cron.log # Logger les messages kernel kern.* /var/log/kern.log # Logger tous les messages d'urgence *.emerg :omusrmsg:* # Rotation et rétention $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat EOF systemctl restart rsyslog # Configuration logrotate pour conservation cat > /etc/logrotate.d/hardening <<'EOF' /var/log/auth.log /var/log/cron.log /var/log/kern.log { rotate 90 daily missingok notifempty compress delaycompress sharedscripts postrotate /usr/lib/rsyslog/rsyslog-rotate endscript } EOF success "Logging configuré" } # ============================================================================ # SCAN ET AUDIT FINAL # ============================================================================ run_security_scan() { log "Exécution du scan de sécurité final..." local report_file="${STATE_DIR}/security_report_$(date +%Y%m%d_%H%M%S).txt" { echo "==========================================" echo "RAPPORT DE SÉCURITÉ" echo "Date: $(date)" echo "Hostname: $(hostname)" echo "==========================================" echo "" # Lynis if command -v lynis &> /dev/null; then echo "=== AUDIT LYNIS ===" lynis audit system --quick --quiet echo "" fi # Vérification des paquets echo "=== VÉRIFICATION INTÉGRITÉ PAQUETS ===" debsums -s 2>&1 | head -20 echo "" # Rootkit check if command -v chkrootkit &> /dev/null; then echo "=== SCAN ROOTKIT (chkrootkit) ===" chkrootkit -q 2>&1 | head -20 echo "" fi if command -v rkhunter &> /dev/null; then echo "=== SCAN ROOTKIT (rkhunter) ===" rkhunter --check --skip-keypress --report-warnings-only 2>&1 | head -20 echo "" fi # Ports ouverts echo "=== PORTS OUVERTS ===" ss -tulpn echo "" # Services actifs echo "=== SERVICES ACTIFS ===" systemctl list-units --type=service --state=running echo "" # Utilisateurs avec shell echo "=== UTILISATEURS AVEC SHELL ===" grep -v "/nologin\|/false" /etc/passwd echo "" # Dernières connexions echo "=== DERNIÈRES CONNEXIONS ===" last -n 20 echo "" } | tee "$report_file" success "Rapport de sécurité généré: $report_file" } # ============================================================================ # FONCTIONS D'AIDE ET D'INFORMATION # ============================================================================ show_help() { cat </dev/null || echo "inconnue") echo -e "${GREEN}[✓]${NC} $task (${timestamp})" else echo -e "${RED}[✗]${NC} $task" fi done } list_tasks() { echo "Tâches disponibles:" echo " - backup_configs" echo " - update_system" echo " - install_tools" echo " - fail2ban" echo " - ufw" echo " - ssh" echo " - sysctl" echo " - password_policy" echo " - limits" echo " - disable_services" echo " - clamav" echo " - aide" echo " - auto_updates" echo " - auditd" echo " - apparmor" echo " - grub" echo " - partitions" echo " - logging" echo " - security_scan" } reset_all_tasks() { warn "Réinitialisation de toutes les tâches..." rm -rf "$STATE_DIR"/*.done "$STATE_DIR"/*.timestamp success "Toutes les tâches ont été réinitialisées" } # ============================================================================ # FONCTION PRINCIPALE # ============================================================================ main() { local force_mode=false local skip_tasks=() local only_task="" # Parser les arguments while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -v|--version) echo "Version $SCRIPT_VERSION" exit 0 ;; -f|--force) force_mode=true shift ;; -s|--skip) skip_tasks+=("$2") shift 2 ;; -o|--only) only_task="$2" shift 2 ;; --status) show_status exit 0 ;; --reset) check_root reset_all_tasks exit 0 ;; --list-tasks) list_tasks exit 0 ;; *) error "Option inconnue: $1" show_help exit 1 ;; esac done # Initialisation mkdir -p "$STATE_DIR" touch "$LOG_FILE" log "========================================" log "Script de Durcissement Linux v${SCRIPT_VERSION}" log "Début: $(date)" log "========================================" echo "" # Vérifications préliminaires check_root check_distribution check_disk_space check_internet_connectivity # Acquérir le verrou acquire_lock # Si mode "only", exécuter uniquement la tâche spécifiée if [[ -n "$only_task" ]]; then info "Mode: exécution de la tâche '$only_task' uniquement" case $only_task in backup_configs) run_task "backup_configs" backup_configs "$force_mode" ;; update_system) run_task "update_system" update_system "$force_mode" ;; install_tools) run_task "install_tools" install_security_tools "$force_mode" ;; fail2ban) run_task "fail2ban" configure_fail2ban "$force_mode" ;; ufw) run_task "ufw" configure_ufw "$force_mode" ;; ssh) run_task "ssh" configure_ssh "$force_mode" ;; sysctl) run_task "sysctl" configure_sysctl "$force_mode" ;; password_policy) run_task "password_policy" configure_password_policy "$force_mode" ;; limits) run_task "limits" configure_limits "$force_mode" ;; disable_services) run_task "disable_services" disable_unnecessary_services "$force_mode" ;; clamav) run_task "clamav" configure_clamav "$force_mode" ;; aide) run_task "aide" configure_aide "$force_mode" ;; auto_updates) run_task "auto_updates" configure_auto_updates "$force_mode" ;; auditd) run_task "auditd" configure_auditd "$force_mode" ;; apparmor) run_task "apparmor" configure_apparmor "$force_mode" ;; grub) run_task "grub" secure_grub "$force_mode" ;; partitions) run_task "partitions" secure_partitions "$force_mode" ;; logging) run_task "logging" configure_logging "$force_mode" ;; security_scan) run_task "security_scan" run_security_scan "$force_mode" ;; *) error "Tâche inconnue: $only_task" list_tasks exit 1 ;; esac exit 0 fi # Exécution normale de toutes les tâches info "Mode: exécution complète (utilisez --only pour exécuter une seule tâche)" # Tableau des tâches à exécuter (dans l'ordre) local task_execution_plan=( "backup_configs:Sauvegarde des configurations" "update_system:Mise à jour système" "install_tools:Installation outils sécurité" "ssh:Configuration SSH sécurisée" "ufw:Configuration pare-feu UFW" "fail2ban:Configuration Fail2ban" "sysctl:Configuration paramètres kernel" "password_policy:Politique mots de passe" "limits:Limites système" "disable_services:Désactivation services" "clamav:Configuration antivirus" "aide:Configuration détection intrusion" "auto_updates:Mises à jour automatiques" "auditd:Configuration audit système" "apparmor:Configuration AppArmor" "grub:Sécurisation GRUB" "partitions:Sécurisation partitions" "logging:Configuration logs" "security_scan:Scan sécurité final" ) # Exécution des tâches local total_tasks=${#task_execution_plan[@]} local current_task=0 local failed_tasks=() for task_entry in "${task_execution_plan[@]}"; do IFS=':' read -r task_name task_description <<< "$task_entry" current_task=$((current_task + 1)) # Vérifier si la tâche doit être ignorée if [[ " ${skip_tasks[*]} " == *" $task_name "* ]]; then info "[$current_task/$total_tasks] Ignoré: $task_description" continue fi info "[$current_task/$total_tasks] Début: $task_description" # Exécuter la tâche case $task_name in backup_configs) run_task "$task_name" backup_configs "$force_mode" || failed_tasks+=("$task_name") ;; update_system) run_task "$task_name" update_system "$force_mode" || failed_tasks+=("$task_name") ;; install_tools) run_task "$task_name" install_security_tools "$force_mode" || failed_tasks+=("$task_name") ;; ssh) run_task "$task_name" configure_ssh "$force_mode" || failed_tasks+=("$task_name") ;; ufw) run_task "$task_name" configure_ufw "$force_mode" || failed_tasks+=("$task_name") ;; fail2ban) run_task "$task_name" configure_fail2ban "$force_mode" || failed_tasks+=("$task_name") ;; sysctl) run_task "$task_name" configure_sysctl "$force_mode" || failed_tasks+=("$task_name") ;; password_policy) run_task "$task_name" configure_password_policy "$force_mode" || failed_tasks+=("$task_name") ;; limits) run_task "$task_name" configure_limits "$force_mode" || failed_tasks+=("$task_name") ;; disable_services) run_task "$task_name" disable_unnecessary_services "$force_mode" || failed_tasks+=("$task_name") ;; clamav) run_task "$task_name" configure_clamav "$force_mode" || failed_tasks+=("$task_name") ;; aide) run_task "$task_name" configure_aide "$force_mode" || failed_tasks+=("$task_name") ;; auto_updates) run_task "$task_name" configure_auto_updates "$force_mode" || failed_tasks+=("$task_name") ;; auditd) run_task "$task_name" configure_auditd "$force_mode" || failed_tasks+=("$task_name") ;; apparmor) run_task "$task_name" configure_apparmor "$force_mode" || failed_tasks+=("$task_name") ;; grub) run_task "$task_name" secure_grub "$force_mode" || failed_tasks+=("$task_name") ;; partitions) run_task "$task_name" secure_partitions "$force_mode" || failed_tasks+=("$task_name") ;; logging) run_task "$task_name" configure_logging "$force_mode" || failed_tasks+=("$task_name") ;; security_scan) run_task "$task_name" run_security_scan "$force_mode" || failed_tasks+=("$task_name") ;; esac echo "" done # Résumé final log "========================================" log "EXÉCUTION TERMINÉE" log "Date: $(date)" log "Durée: ~$(($SECONDS / 60)) minutes" log "========================================" if [[ ${#failed_tasks[@]} -eq 0 ]]; then success "Toutes les tâches ont été exécutées avec succès!" success "Le système a été durci avec succès." # Afficher les recommandations finales echo "" info "=== RECOMMANDATIONS FINALES ===" info "1. Testez votre connexion SSH sur le port ${SSH_PORT}" info "2. Vérifiez les règles UFW: ufw status verbose" info "3. Testez Fail2ban: fail2ban-client status" info "4. Redémarrez le système pour appliquer tous les changements" info "5. Consultez le rapport de sécurité dans: ${STATE_DIR}/" echo "" warn "IMPORTANT: Sauvegardez vos clés SSH et mots de passe!" warn "Le redémarrage est recommandé pour appliquer tous les changements." else error "Certaines tâches ont échoué: ${failed_tasks[*]}" warn "Consultez le fichier de log: $LOG_FILE" warn "Vous pouvez réessayer les tâches échouées avec: $0 --only " fi echo "" info "Fichier de log complet: $LOG_FILE" info "État des tâches: $0 --status" # Libérer le verrou release_lock } # ============================================================================ # POINT D'ENTRÉE DU SCRIPT # ============================================================================ # Vérifier que le script n'est pas sourcé if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi # ============================================================================ # NOTES DE SÉCURITÉ FINALES # ============================================================================ # Ce script effectue les actions suivantes: # 1. Sauvegarde des configurations existantes # 2. Mise à jour complète du système # 3. Installation des outils de sécurité essentiels # 4. Configuration SSH sécurisée (port personnalisé, désactivation root login) # 5. Configuration du pare-feu UFW avec règles restrictives # 6. Configuration Fail2ban pour prévention des attaques par force brute # 7. Hardening des paramètres kernel via sysctl # 8. Politique de mots de passe stricte # 9. Limites système pour prévenir les DoS # 10. Désactivation des services non nécessaires # 11. Configuration antivirus ClamAV # 12. Configuration AIDE pour détection d'intrusion # 13. Mises à jour automatiques de sécurité # 14. Configuration auditd pour audit système # 15. Activation d'AppArmor # 16. Sécurisation du bootloader GRUB # 17. Recommandations pour sécurisation des partitions # 18. Configuration avancée des logs # 19. Scan de sécurité final avec génération de rapport # AVERTISSEMENT: # - Testez toujours dans un environnement de test avant la production # - Assurez-vous d'avoir un accès de secours (console, KVM, etc.) # - Conservez une sauvegarde fonctionnelle du système # - Certaines configurations peuvent nécessiter un redémarrage