1614 lines
59 KiB
Bash
1614 lines
59 KiB
Bash
#!/bin/bash
|
||
|
||
################################################################################
|
||
# Script: system_hardening_optimized.sh
|
||
# Version: 6.0
|
||
# Date: $(date +%Y-%m-%d)
|
||
# Author: Security Team
|
||
# Description: Système de durcissement sécurité pour Debian/Ubuntu LTS
|
||
# License: GPLv3
|
||
################################################################################
|
||
|
||
set -euo pipefail
|
||
|
||
# ==============================================================================
|
||
# CONFIGURATION
|
||
# ==============================================================================
|
||
|
||
readonly LOG_FILE="/var/log/system_hardening.log"
|
||
readonly STATUS_FILE="/var/log/hardening_status.log"
|
||
readonly BACKUP_DIR="/root/backup_hardening_$(date +%Y%m%d_%H%M%S)"
|
||
readonly SECURITY_REPORT="/var/log/security_report_$(date +%Y%m%d).log"
|
||
readonly NEW_SSH_PORT=22022
|
||
|
||
TOTAL_STEPS=30
|
||
CURRENT_STEP=1
|
||
|
||
# ==============================================================================
|
||
# COULEURS
|
||
# ==============================================================================
|
||
|
||
readonly RED='\033[0;31m'
|
||
readonly GREEN='\033[0;32m'
|
||
readonly YELLOW='\033[1;33m'
|
||
readonly CYAN='\033[0;36m'
|
||
readonly BLUE='\033[0;34m'
|
||
readonly MAGENTA='\033[0;35m'
|
||
readonly NC='\033[0m'
|
||
|
||
# ==============================================================================
|
||
# FONCTIONS UTILITAIRES
|
||
# ==============================================================================
|
||
|
||
log_message() {
|
||
local message="$1"
|
||
local level="${2:-INFO}"
|
||
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
|
||
}
|
||
|
||
print_step() {
|
||
local step_title="$1"
|
||
echo -e "\n${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}"
|
||
echo -e "${YELLOW} ÉTAPE ${CURRENT_STEP}/${TOTAL_STEPS}: $step_title${NC}"
|
||
echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}"
|
||
log_message "Début étape $CURRENT_STEP: $step_title" "STEP"
|
||
CURRENT_STEP=$((CURRENT_STEP + 1))
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✓${NC} $1"
|
||
log_message "$1" "SUCCESS"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠${NC} $1"
|
||
log_message "$1" "WARNING"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}✗${NC} $1" >&2
|
||
log_message "$1" "ERROR"
|
||
}
|
||
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ${NC} $1"
|
||
log_message "$1" "INFO"
|
||
}
|
||
|
||
check_step_done() {
|
||
grep -q "^${1}$" "$STATUS_FILE" 2>/dev/null
|
||
}
|
||
|
||
mark_step_done() {
|
||
echo "$1" >> "$STATUS_FILE"
|
||
log_message "Étape '$1' marquée comme terminée" "STATUS"
|
||
}
|
||
|
||
skip_step() {
|
||
print_info "Étape déjà effectuée : $1"
|
||
CURRENT_STEP=$((CURRENT_STEP + 1))
|
||
}
|
||
|
||
detect_container() {
|
||
grep -qE "lxc|container" /proc/self/cgroup 2>/dev/null || \
|
||
[[ -f /.dockerenv ]] || [[ -d /dev/lxc ]]
|
||
}
|
||
|
||
backup_file() {
|
||
local file_path="$1"
|
||
[[ -f "$file_path" ]] && {
|
||
mkdir -p "$BACKUP_DIR"
|
||
cp "$file_path" "${BACKUP_DIR}/"
|
||
log_message "Sauvegarde de $file_path" "BACKUP"
|
||
}
|
||
}
|
||
|
||
add_unique_line() {
|
||
local line="$1"
|
||
local file="$2"
|
||
grep -qF "$line" "$file" 2>/dev/null || {
|
||
echo "$line" >> "$file"
|
||
log_message "Ajout ligne: '$line' -> $file" "CONFIG"
|
||
}
|
||
}
|
||
|
||
update_config_value() {
|
||
local file="$1"
|
||
local key="$2"
|
||
local value="$3"
|
||
backup_file "$file"
|
||
|
||
if grep -q "^${key}" "$file"; then
|
||
sed -i "s/^${key}.*/${key} ${value}/" "$file"
|
||
else
|
||
echo "${key} ${value}" >> "$file"
|
||
fi
|
||
}
|
||
|
||
# ==============================================================================
|
||
# FONCTIONS DE DURCISSEMENT
|
||
# ==============================================================================
|
||
|
||
# ÉTAPE 1: Mise à jour système et installation outils
|
||
install_security_tools() {
|
||
local step_name="install_security_tools"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Mise à jour système et installation outils de sécurité"
|
||
|
||
print_info "Mise à jour des dépôts..."
|
||
DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
||
|
||
print_info "Mise à jour du système..."
|
||
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq
|
||
|
||
local packages="lynis aide aide-common fail2ban ufw libpam-pwquality apt-listchanges \
|
||
apt-listbugs needrestart clamav clamav-daemon chrony chkrootkit \
|
||
libpam-tmpdir debsums unattended-upgrades"
|
||
|
||
detect_container || packages+=" acct"
|
||
|
||
print_info "Installation des paquets de sécurité..."
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $packages
|
||
|
||
print_success "Système mis à jour et outils installés"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 2: Configuration Process Accounting
|
||
configure_process_accounting() {
|
||
local step_name="configure_process_accounting"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration du Process Accounting"
|
||
|
||
if detect_container; then
|
||
print_warning "Conteneur détecté - Process Accounting désactivé"
|
||
for service in acct.service psacct.service; do
|
||
systemctl list-unit-files | grep -q "^${service}" && {
|
||
systemctl stop "$service" 2>/dev/null || true
|
||
systemctl disable "$service" 2>/dev/null || true
|
||
systemctl mask "$service" 2>/dev/null || true
|
||
}
|
||
done
|
||
else
|
||
systemctl enable acct.service 2>/dev/null && \
|
||
systemctl start acct.service 2>/dev/null && \
|
||
print_success "Process Accounting activé" || \
|
||
print_warning "Process Accounting non disponible"
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 3: Durcissement sysctl
|
||
configure_sysctl_security() {
|
||
local step_name="configure_sysctl_security"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Durcissement des paramètres noyau (sysctl)"
|
||
|
||
cat > /etc/sysctl.d/99-security-hardening.conf << 'EOF'
|
||
# Sécurité réseau IPv4
|
||
net.ipv4.conf.all.accept_source_route = 0
|
||
net.ipv4.conf.all.accept_redirects = 0
|
||
net.ipv4.conf.all.send_redirects = 0
|
||
net.ipv4.conf.all.log_martians = 1
|
||
net.ipv4.conf.all.rp_filter = 1
|
||
net.ipv4.conf.default.accept_source_route = 0
|
||
net.ipv4.conf.default.accept_redirects = 0
|
||
net.ipv4.conf.default.log_martians = 1
|
||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||
net.ipv4.tcp_syncookies = 1
|
||
|
||
# Sécurité réseau IPv6
|
||
net.ipv6.conf.all.accept_redirects = 0
|
||
net.ipv6.conf.all.accept_source_route = 0
|
||
net.ipv6.conf.default.accept_redirects = 0
|
||
net.ipv6.conf.default.accept_source_route = 0
|
||
|
||
# Sécurité noyau
|
||
kernel.randomize_va_space = 2
|
||
kernel.kptr_restrict = 2
|
||
kernel.dmesg_restrict = 1
|
||
kernel.yama.ptrace_scope = 1
|
||
kernel.unprivileged_bpf_disabled = 1
|
||
|
||
# Sécurité système
|
||
fs.suid_dumpable = 0
|
||
fs.protected_fifos = 2
|
||
fs.protected_regular = 2
|
||
fs.protected_symlinks = 1
|
||
fs.protected_hardlinks = 1
|
||
EOF
|
||
|
||
sysctl -p /etc/sysctl.d/99-security-hardening.conf 2>/dev/null || \
|
||
print_warning "Certains paramètres sysctl ignorés"
|
||
|
||
print_success "Paramètres sysctl appliqués"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 4: Configuration permissions logs
|
||
configure_log_permissions() {
|
||
local step_name="configure_log_permissions"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration des permissions des fichiers de log"
|
||
|
||
find /var/log -type f -exec chmod 640 {} \; 2>/dev/null || true
|
||
[[ -f /var/log/auth.log ]] && chmod 600 /var/log/auth.log
|
||
[[ -f /var/log/syslog ]] && chmod 600 /var/log/syslog
|
||
chown root:adm /var/log/ 2>/dev/null || true
|
||
chmod 750 /var/log/ 2>/dev/null || true
|
||
|
||
print_success "Permissions des logs configurées"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 5: Politique mots de passe PAM
|
||
configure_pam_password_policy() {
|
||
local step_name="configure_pam_password_policy"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration de la politique de mots de passe PAM"
|
||
|
||
backup_file "/etc/security/pwquality.conf"
|
||
|
||
cat >> /etc/security/pwquality.conf << 'EOF'
|
||
|
||
# Configuration renforcée
|
||
minlen = 14
|
||
minclass = 3
|
||
maxrepeat = 3
|
||
maxsequence = 3
|
||
dcredit = -1
|
||
ucredit = -1
|
||
lcredit = -1
|
||
ocredit = -1
|
||
difok = 3
|
||
EOF
|
||
|
||
backup_file "/etc/pam.d/common-password"
|
||
|
||
# Ajouter remember et sha512
|
||
sed -i 's/pam_unix.so.*/& remember=5 sha512 rounds=500000/' /etc/pam.d/common-password
|
||
|
||
# Ajouter pwquality si absent
|
||
grep -q "pam_pwquality.so" /etc/pam.d/common-password || \
|
||
sed -i '1i password requisite pam_pwquality.so retry=3' /etc/pam.d/common-password
|
||
|
||
print_success "Politique PAM configurée"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 6: Configuration login.defs
|
||
configure_login_defs() {
|
||
local step_name="configure_login_defs"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration des paramètres de connexion (login.defs)"
|
||
|
||
backup_file "/etc/login.defs"
|
||
|
||
update_config_value "/etc/login.defs" "PASS_MAX_DAYS" "90"
|
||
update_config_value "/etc/login.defs" "PASS_MIN_DAYS" "7"
|
||
update_config_value "/etc/login.defs" "PASS_WARN_AGE" "7"
|
||
update_config_value "/etc/login.defs" "LOGIN_RETRIES" "3"
|
||
update_config_value "/etc/login.defs" "LOGIN_TIMEOUT" "60"
|
||
update_config_value "/etc/login.defs" "UMASK" "027"
|
||
update_config_value "/etc/login.defs" "ENCRYPT_METHOD" "SHA512"
|
||
update_config_value "/etc/login.defs" "SHA_CRYPT_MIN_ROUNDS" "500000"
|
||
update_config_value "/etc/login.defs" "SHA_CRYPT_MAX_ROUNDS" "1000000"
|
||
|
||
print_success "Configuration login.defs appliquée"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 7: Configuration umask global
|
||
configure_umask() {
|
||
local step_name="configure_umask"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration de l'umask par défaut"
|
||
|
||
for file in /etc/profile /etc/bash.bashrc; do
|
||
backup_file "$file"
|
||
sed -i '/^umask/d' "$file"
|
||
add_unique_line "umask 027" "$file"
|
||
done
|
||
|
||
print_success "Umask configuré à 027"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 8: Configuration AIDE SHA512
|
||
configure_aide_sha512() {
|
||
local step_name="configure_aide_sha512"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration AIDE pour SHA512"
|
||
|
||
backup_file "/etc/aide/aide.conf"
|
||
|
||
grep -q "ALLXTRAHASHES" /etc/aide/aide.conf && \
|
||
sed -i 's/ALLXTRAHASHES.*=.*/ALLXTRAHASHES = sha512/' /etc/aide/aide.conf || \
|
||
echo "ALLXTRAHASHES = sha512" >> /etc/aide/aide.conf
|
||
|
||
print_success "AIDE configuré pour SHA512"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 9: Initialisation AIDE
|
||
initialize_aide_db() {
|
||
local step_name="initialize_aide_db"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Initialisation de la base de données AIDE"
|
||
|
||
print_info "Création de la base de données de référence (peut prendre plusieurs minutes)..."
|
||
|
||
if command -v aideinit > /dev/null; then
|
||
aideinit && print_success "Base de données AIDE initialisée via aideinit" || {
|
||
print_error "Échec aideinit"
|
||
return 1
|
||
}
|
||
else
|
||
aide --init --config /etc/aide/aide.conf && {
|
||
[[ -f /var/lib/aide/aide.db.new ]] && {
|
||
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
|
||
print_success "Base de données AIDE initialisée"
|
||
} || {
|
||
print_error "Fichier de base de données non trouvé"
|
||
return 1
|
||
}
|
||
} || {
|
||
print_error "Échec initialisation AIDE"
|
||
return 1
|
||
}
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 10: Configuration ClamAV
|
||
configure_clamav() {
|
||
local step_name="configure_clamav"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration de ClamAV"
|
||
|
||
print_info "Mise à jour de la base de données ClamAV..."
|
||
systemctl stop clamav-freshclam 2>/dev/null || true
|
||
freshclam || print_warning "Échec mise à jour ClamAV (normal si déjà récent)"
|
||
|
||
systemctl enable clamav-freshclam
|
||
systemctl start clamav-freshclam
|
||
systemctl enable clamav-daemon 2>/dev/null || true
|
||
systemctl start clamav-daemon 2>/dev/null || true
|
||
|
||
print_success "ClamAV configuré"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 11: Configuration Chrony
|
||
configure_chrony() {
|
||
local step_name="configure_chrony"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration de la synchronisation horaire (Chrony)"
|
||
|
||
# Vérifier si on est dans un conteneur
|
||
if detect_container; then
|
||
print_warning "Conteneur détecté - synchronisation horaire gérée par l'hôte"
|
||
print_info "La configuration de Chrony est ignorée en environnement conteneurisé"
|
||
|
||
# Désactiver/masquer chrony s'il est présent
|
||
if systemctl list-unit-files | grep -q "^chrony.service"; then
|
||
systemctl stop chrony 2>/dev/null || true
|
||
systemctl disable chrony 2>/dev/null || true
|
||
systemctl mask chrony 2>/dev/null || true
|
||
print_info "Service Chrony désactivé (non nécessaire en conteneur)"
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
return 0
|
||
fi
|
||
|
||
# Configuration pour système physique/VM
|
||
backup_file "/etc/chrony/chrony.conf"
|
||
|
||
# Configurer le fuseau horaire
|
||
timedatectl set-timezone Europe/Paris || {
|
||
print_warning "Impossible de définir le fuseau horaire"
|
||
}
|
||
|
||
cat > /etc/chrony/chrony.conf << 'EOF'
|
||
# Serveurs NTP
|
||
pool 2.debian.pool.ntp.org iburst
|
||
server 0.fr.pool.ntp.org iburst
|
||
server 1.fr.pool.ntp.org iburst
|
||
|
||
# Fichiers de drift et log
|
||
driftfile /var/lib/chrony/chrony.drift
|
||
logdir /var/log/chrony
|
||
|
||
# Restrictions
|
||
makestep 1.0 3
|
||
rtcsync
|
||
EOF
|
||
|
||
# Activer et démarrer le service
|
||
if systemctl enable chrony 2>/dev/null && systemctl restart chrony 2>/dev/null; then
|
||
print_success "Chrony configuré et démarré"
|
||
else
|
||
print_warning "Erreur lors du démarrage de Chrony - service peut-être non disponible"
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 12: Durcissement SSH
|
||
harden_ssh() {
|
||
local step_name="harden_ssh"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Durcissement du service SSH"
|
||
|
||
backup_file "/etc/ssh/sshd_config"
|
||
|
||
local sshd_config="/etc/ssh/sshd_config"
|
||
|
||
# Fonction helper pour mettre à jour une valeur
|
||
update_ssh_param() {
|
||
local param="$1"
|
||
local value="$2"
|
||
local file="$3"
|
||
|
||
# Supprimer toutes les occurrences (commentées ou non)
|
||
sed -i "/^#*${param}/d" "$file"
|
||
# Ajouter la nouvelle valeur
|
||
echo "${param} ${value}" >> "$file"
|
||
}
|
||
|
||
# Configuration des ports
|
||
sed -i '/^Port /d' "$sshd_config"
|
||
sed -i '/^#Port /d' "$sshd_config"
|
||
echo "Port 22" >> "$sshd_config"
|
||
echo "Port $NEW_SSH_PORT" >> "$sshd_config"
|
||
|
||
# Paramètres de sécurité avec la fonction helper
|
||
update_ssh_param "PermitRootLogin" "yes" "$sshd_config"
|
||
update_ssh_param "PasswordAuthentication" "no" "$sshd_config"
|
||
update_ssh_param "PubkeyAuthentication" "yes" "$sshd_config"
|
||
update_ssh_param "PermitEmptyPasswords" "no" "$sshd_config"
|
||
update_ssh_param "X11Forwarding" "no" "$sshd_config"
|
||
update_ssh_param "MaxAuthTries" "3" "$sshd_config"
|
||
update_ssh_param "ClientAliveInterval" "300" "$sshd_config"
|
||
update_ssh_param "ClientAliveCountMax" "2" "$sshd_config"
|
||
update_ssh_param "LoginGraceTime" "60" "$sshd_config"
|
||
update_ssh_param "MaxSessions" "2" "$sshd_config"
|
||
update_ssh_param "TCPKeepAlive" "no" "$sshd_config"
|
||
update_ssh_param "AllowAgentForwarding" "no" "$sshd_config"
|
||
update_ssh_param "AllowTcpForwarding" "no" "$sshd_config"
|
||
update_ssh_param "LogLevel" "VERBOSE" "$sshd_config"
|
||
update_ssh_param "Compression" "no" "$sshd_config"
|
||
update_ssh_param "Protocol" "2" "$sshd_config"
|
||
|
||
# Algorithmes sécurisés (s'assurer qu'ils n'existent pas déjà)
|
||
sed -i '/^Ciphers /d' "$sshd_config"
|
||
sed -i '/^MACs /d' "$sshd_config"
|
||
sed -i '/^KexAlgorithms /d' "$sshd_config"
|
||
|
||
cat >> "$sshd_config" << 'EOF'
|
||
|
||
# Algorithmes cryptographiques sécurisés
|
||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
|
||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
|
||
EOF
|
||
|
||
# Bannière
|
||
update_ssh_param "Banner" "/etc/issue.net" "$sshd_config"
|
||
|
||
# Test de la configuration avant application
|
||
print_info "Test de la configuration SSH..."
|
||
if sshd -t 2>&1 | tee -a "$LOG_FILE"; then
|
||
print_success "Configuration SSH valide"
|
||
|
||
# Redémarrer le service
|
||
print_info "Redémarrage du service SSH..."
|
||
if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then
|
||
print_success "SSH durci avec succès"
|
||
|
||
# Afficher les ports configurés
|
||
local ports=$(grep "^Port" "$sshd_config" | awk '{print $2}' | tr '\n' ' ')
|
||
print_info "Ports SSH actifs: $ports"
|
||
print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_warning "⚠ TESTEZ IMMÉDIATEMENT dans un NOUVEAU terminal:"
|
||
print_warning " ssh -p $NEW_SSH_PORT $(whoami)@$(hostname -I | awk '{print $1}')"
|
||
print_warning " NE FERMEZ PAS cette session avant validation!"
|
||
print_warning "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
else
|
||
print_error "Échec redémarrage SSH"
|
||
print_error "Restauration de la configuration..."
|
||
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
|
||
systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null
|
||
return 1
|
||
fi
|
||
else
|
||
print_error "Configuration SSH invalide - vérifiez les logs"
|
||
print_error "Restauration de la sauvegarde..."
|
||
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
|
||
return 1
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 13: Configuration bannières
|
||
configure_banners() {
|
||
local step_name="configure_banners"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration des bannières légales"
|
||
|
||
local banner_text="╔════════════════════════════════════════════════════════════╗
|
||
║ SYSTÈME SÉCURISÉ ║
|
||
║ ║
|
||
║ Accès réservé aux personnes autorisées uniquement. ║
|
||
║ Toute tentative d'accès non autorisée est interdite ║
|
||
║ et sera tracée et poursuivie selon la loi. ║
|
||
║ ║
|
||
║ Les activités sont surveillées et enregistrées. ║
|
||
╚════════════════════════════════════════════════════════════╝"
|
||
|
||
echo "$banner_text" | tee /etc/issue /etc/issue.net /etc/motd > /dev/null
|
||
|
||
print_success "Bannières légales configurées"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 14: Configuration UFW
|
||
configure_ufw_firewall() {
|
||
local step_name="configure_ufw_firewall"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration du pare-feu UFW"
|
||
|
||
# Vérifier si on est dans un conteneur
|
||
if detect_container; then
|
||
print_warning "Conteneur détecté - pare-feu géré par l'hôte Proxmox"
|
||
print_info "UFW ne peut pas être configuré dans un conteneur LXC"
|
||
print_info "Les règles de pare-feu doivent être configurées au niveau de l'hôte"
|
||
|
||
# Désactiver UFW s'il est actif
|
||
if systemctl is-active --quiet ufw 2>/dev/null; then
|
||
ufw --force disable 2>/dev/null || true
|
||
systemctl stop ufw 2>/dev/null || true
|
||
systemctl disable ufw 2>/dev/null || true
|
||
systemctl mask ufw 2>/dev/null || true
|
||
print_info "Service UFW désactivé (non nécessaire en conteneur)"
|
||
fi
|
||
|
||
print_success "Configuration pare-feu ignorée (environnement conteneurisé)"
|
||
mark_step_done "$step_name"
|
||
return 0
|
||
fi
|
||
|
||
# Configuration pour système physique/VM
|
||
backup_file "/etc/ufw/user.rules"
|
||
backup_file "/etc/ufw/user6.rules"
|
||
|
||
print_info "Réinitialisation des règles UFW..."
|
||
ufw --force reset > /dev/null 2>&1 || {
|
||
print_error "Impossible de réinitialiser UFW"
|
||
mark_step_done "$step_name"
|
||
return 1
|
||
}
|
||
|
||
print_info "Configuration des politiques par défaut..."
|
||
ufw default deny incoming
|
||
ufw default allow outgoing
|
||
|
||
# Ports SSH
|
||
print_info "Autorisation des ports SSH..."
|
||
ufw allow 22/tcp comment 'SSH temporaire' || print_warning "Erreur ajout règle SSH:22"
|
||
ufw allow "${NEW_SSH_PORT}/tcp" comment 'SSH sécurisé' || print_warning "Erreur ajout règle SSH:${NEW_SSH_PORT}"
|
||
|
||
# Limiter ICMP
|
||
print_info "Configuration limitation ICMP..."
|
||
if [[ -f /etc/ufw/before.rules ]]; then
|
||
backup_file "/etc/ufw/before.rules"
|
||
|
||
# Vérifier si la règle n'existe pas déjà
|
||
if ! grep -q "Limiter pings" /etc/ufw/before.rules; then
|
||
cat >> /etc/ufw/before.rules << 'EOF'
|
||
|
||
# Limiter pings
|
||
-A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 3/second --limit-burst 5 -j ACCEPT
|
||
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP
|
||
EOF
|
||
fi
|
||
fi
|
||
|
||
# Activer UFW
|
||
print_info "Activation du pare-feu..."
|
||
if echo "y" | ufw --force enable > /dev/null 2>&1; then
|
||
print_success "Pare-feu UFW configuré et activé"
|
||
else
|
||
# Fallback
|
||
if ufw enable <<< 'y' > /dev/null 2>&1; then
|
||
print_success "Pare-feu UFW configuré et activé (fallback)"
|
||
else
|
||
print_warning "Échec activation UFW - continuer sans..."
|
||
fi
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 15: Configuration Fail2ban
|
||
configure_fail2ban() {
|
||
local step_name="configure_fail2ban"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration de Fail2ban"
|
||
|
||
backup_file "/etc/fail2ban/jail.conf"
|
||
|
||
# Configuration adaptée selon l'environnement
|
||
if detect_container; then
|
||
print_warning "Conteneur détecté - configuration Fail2ban limitée"
|
||
|
||
cat > /etc/fail2ban/jail.local << EOF
|
||
[DEFAULT]
|
||
bantime = 1h
|
||
findtime = 10m
|
||
maxretry = 3
|
||
ignoreip = 127.0.0.1/8 ::1
|
||
backend = systemd
|
||
|
||
# Mode conteneur - pas d'action iptables
|
||
banaction = %(banaction_allports)s
|
||
banaction_allports = iptables-multiport
|
||
|
||
[sshd]
|
||
enabled = true
|
||
port = 22,${NEW_SSH_PORT}
|
||
filter = sshd
|
||
logpath = /var/log/auth.log
|
||
maxretry = 5
|
||
bantime = 1h
|
||
# Action désactivée en conteneur (pas d'accès iptables)
|
||
action = %(action_)s
|
||
EOF
|
||
print_info "Fail2ban configuré en mode conteneur (logging seul, sans bannissement iptables)"
|
||
else
|
||
# Configuration complète pour système physique/VM
|
||
cat > /etc/fail2ban/jail.local << EOF
|
||
[DEFAULT]
|
||
bantime = 1h
|
||
findtime = 10m
|
||
maxretry = 3
|
||
ignoreip = 127.0.0.1/8 ::1
|
||
backend = systemd
|
||
|
||
[sshd]
|
||
enabled = true
|
||
port = 22,${NEW_SSH_PORT}
|
||
filter = sshd
|
||
logpath = /var/log/auth.log
|
||
maxretry = 5
|
||
bantime = 1h
|
||
|
||
[apache-auth]
|
||
enabled = true
|
||
port = http,https
|
||
logpath = /var/log/apache*/*error.log
|
||
maxretry = 3
|
||
EOF
|
||
print_info "Fail2ban configuré en mode complet avec bannissement iptables"
|
||
fi
|
||
|
||
# Activer et démarrer le service
|
||
systemctl enable fail2ban 2>&1 | tee -a "$LOG_FILE" || true
|
||
|
||
if systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE"; then
|
||
print_success "Fail2ban configuré et démarré"
|
||
|
||
# Vérifier le statut
|
||
sleep 2
|
||
if systemctl is-active --quiet fail2ban; then
|
||
print_info "Statut Fail2ban: $(systemctl is-active fail2ban)"
|
||
else
|
||
print_warning "Fail2ban démarré mais statut incertain"
|
||
fi
|
||
else
|
||
print_warning "Problème au démarrage de Fail2ban - vérifiez les logs: journalctl -xeu fail2ban"
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 16: Suppression paquets inutiles
|
||
remove_unneeded_packages() {
|
||
local step_name="remove_unneeded_packages"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Suppression des paquets inutiles"
|
||
|
||
local packages_to_remove=(telnet rsh-client rsh-server netcat-openbsd netcat-traditional nis talk talkd)
|
||
|
||
for package in "${packages_to_remove[@]}"; do
|
||
dpkg -l | grep -q "^ii.*${package}" && {
|
||
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "$package" 2>/dev/null
|
||
print_info "Paquet $package supprimé"
|
||
}
|
||
done
|
||
|
||
DEBIAN_FRONTEND=noninteractive apt-get autoremove -y -qq
|
||
|
||
print_success "Paquets inutiles supprimés"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 17: Restriction permissions
|
||
restrict_file_permissions() {
|
||
local step_name="restrict_file_permissions"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Restriction des permissions des fichiers critiques"
|
||
|
||
chmod 644 /etc/passwd 2>/dev/null || true
|
||
chmod 600 /etc/shadow 2>/dev/null || true
|
||
chmod 644 /etc/group 2>/dev/null || true
|
||
chmod 600 /etc/gshadow 2>/dev/null || true
|
||
chmod 600 /etc/sudoers 2>/dev/null || true
|
||
chmod 750 /etc/sudoers.d 2>/dev/null || true
|
||
chmod 600 /boot/grub/grub.cfg 2>/dev/null || true
|
||
chmod 644 /etc/ssh/ssh_config 2>/dev/null || true
|
||
chmod 600 /etc/ssh/sshd_config 2>/dev/null || true
|
||
|
||
print_success "Permissions des fichiers critiques restreintes"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 18: Désactivation modules noyau
|
||
disable_risky_kernel_modules() {
|
||
local step_name="disable_risky_kernel_modules"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Désactivation des modules noyau risqués"
|
||
|
||
cat > /etc/modprobe.d/hardening-blacklist.conf << 'EOF'
|
||
# Protocoles réseau non utilisés
|
||
blacklist dccp
|
||
install dccp /bin/true
|
||
blacklist sctp
|
||
install sctp /bin/true
|
||
blacklist rds
|
||
install rds /bin/true
|
||
blacklist tipc
|
||
install tipc /bin/true
|
||
|
||
# Systèmes de fichiers non utilisés
|
||
blacklist cramfs
|
||
install cramfs /bin/true
|
||
blacklist freevxfs
|
||
install freevxfs /bin/true
|
||
blacklist jffs2
|
||
install jffs2 /bin/true
|
||
blacklist hfs
|
||
install hfs /bin/true
|
||
blacklist hfsplus
|
||
install hfsplus /bin/true
|
||
blacklist squashfs
|
||
install squashfs /bin/true
|
||
blacklist udf
|
||
install udf /bin/true
|
||
|
||
# FireWire (DMA attack)
|
||
blacklist firewire-core
|
||
install firewire-core /bin/true
|
||
|
||
# Thunderbolt (DMA attack)
|
||
blacklist thunderbolt
|
||
install thunderbolt /bin/true
|
||
EOF
|
||
|
||
detect_container && \
|
||
print_warning "Conteneur - modules gérés par l'hôte" || \
|
||
print_info "Modules blacklistés"
|
||
|
||
print_success "Modules noyau risqués désactivés"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 19: Configuration limites sécurité
|
||
configure_security_limits() {
|
||
local step_name="configure_security_limits"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration des limites de sécurité"
|
||
|
||
backup_file "/etc/security/limits.conf"
|
||
|
||
cat >> /etc/security/limits.conf << 'EOF'
|
||
|
||
# Désactiver core dumps
|
||
* hard core 0
|
||
|
||
# Limiter les processus
|
||
* soft nproc 512
|
||
* hard nproc 1024
|
||
|
||
# Limiter les fichiers ouverts
|
||
* soft nofile 65536
|
||
* hard nofile 65536
|
||
EOF
|
||
|
||
print_success "Limites de sécurité configurées"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 20: Vérification intégrité paquets
|
||
verify_packages_integrity() {
|
||
local step_name="verify_packages_integrity"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Vérification de l'intégrité des paquets"
|
||
|
||
print_info "Vérification avec debsums (peut prendre du temps)..."
|
||
debsums -s 2>&1 | tee -a "$LOG_FILE" || print_warning "Certains paquets ont des erreurs"
|
||
|
||
print_success "Vérification d'intégrité terminée"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 21: Configuration mises à jour auto
|
||
configure_automatic_updates() {
|
||
local step_name="configure_automatic_updates"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration des mises à jour automatiques"
|
||
|
||
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
|
||
Unattended-Upgrade::Allowed-Origins {
|
||
"${distro_id}:${distro_codename}-security";
|
||
"${distro_id}:${distro_codename}-updates";
|
||
};
|
||
|
||
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||
Unattended-Upgrade::MinimalSteps "true";
|
||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||
Unattended-Upgrade::Automatic-Reboot "false";
|
||
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
|
||
Unattended-Upgrade::Mail "root";
|
||
Unattended-Upgrade::MailReport "on-change";
|
||
EOF
|
||
|
||
cat > /etc/apt/apt.conf.d/10periodic << 'EOF'
|
||
APT::Periodic::Update-Package-Lists "1";
|
||
APT::Periodic::Download-Upgradeable-Packages "1";
|
||
APT::Periodic::AutocleanInterval "7";
|
||
APT::Periodic::Unattended-Upgrade "1";
|
||
EOF
|
||
|
||
print_success "Mises à jour automatiques configurées"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 22: Configuration tâche AIDE cron
|
||
configure_aide_cron() {
|
||
local step_name="configure_aide_cron"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration des vérifications AIDE planifiées"
|
||
|
||
cat > /etc/cron.weekly/aide-check << 'EOF'
|
||
#!/bin/bash
|
||
LOGFILE="/var/log/aide-check-$(date +%Y%m%d).log"
|
||
|
||
if command -v aide > /dev/null; then
|
||
echo "=== Vérification AIDE $(date) ===" > "$LOGFILE"
|
||
|
||
if /usr/bin/aide --check >> "$LOGFILE" 2>&1; then
|
||
echo "Vérification réussie" >> "$LOGFILE"
|
||
|
||
if grep -q "added\|changed\|removed" "$LOGFILE"; then
|
||
mail -s "[AIDE] Changements détectés sur $(hostname)" root < "$LOGFILE"
|
||
fi
|
||
else
|
||
echo "Échec vérification" >> "$LOGFILE"
|
||
mail -s "[AIDE] ALERTE - Échec sur $(hostname)" root < "$LOGFILE"
|
||
fi
|
||
|
||
echo "=== Fin vérification $(date) ===" >> "$LOGFILE"
|
||
fi
|
||
EOF
|
||
|
||
chmod 750 /etc/cron.weekly/aide-check
|
||
|
||
print_success "Vérification AIDE hebdomadaire configurée"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 23: Durcissement bannière SMTP
|
||
harden_smtp_banner() {
|
||
local step_name="harden_smtp_banner"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Durcissement bannière SMTP"
|
||
|
||
if dpkg -l | grep -q "^ii.*postfix"; then
|
||
backup_file "/etc/postfix/main.cf"
|
||
postconf -e "smtpd_banner = \$myhostname ESMTP"
|
||
postconf -e "disable_vrfy_command = yes"
|
||
systemctl reload postfix 2>/dev/null || true
|
||
print_success "Bannière Postfix durcie"
|
||
elif dpkg -l | grep -q "^ii.*exim4"; then
|
||
backup_file "/etc/exim4/exim4.conf.template"
|
||
sed -i 's/^smtp_banner.*/smtp_banner = "${primary_hostname} ESMTP"/' \
|
||
/etc/exim4/exim4.conf.template 2>/dev/null || true
|
||
update-exim4.conf 2>/dev/null || true
|
||
print_success "Bannière Exim4 durcie"
|
||
else
|
||
print_info "Aucun serveur SMTP détecté"
|
||
fi
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 24: Durcissement services systemd
|
||
harden_systemd_services() {
|
||
local step_name="harden_systemd_services"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Durcissement services systemd"
|
||
|
||
command -v systemd-analyze > /dev/null || {
|
||
print_warning "systemd-analyze non disponible"
|
||
mark_step_done "$step_name"
|
||
return 0
|
||
}
|
||
|
||
# Vérifier si on est dans un conteneur
|
||
if detect_container; then
|
||
print_warning "Conteneur détecté - durcissement systemd limité"
|
||
print_info "Les namespaces systemd ne sont pas supportés dans les conteneurs LXC"
|
||
print_info "Le durcissement systemd est ignoré pour éviter les erreurs de démarrage"
|
||
|
||
# Nettoyer les éventuelles configurations précédentes qui pourraient causer des problèmes
|
||
local services=(ssh sshd fail2ban chrony)
|
||
for service in "${services[@]}"; do
|
||
local unit="${service}.service"
|
||
local override_dir="/etc/systemd/system/${unit}.d"
|
||
|
||
if [[ -d "$override_dir" ]]; then
|
||
print_info "Nettoyage des overrides systemd pour $unit"
|
||
rm -rf "$override_dir"
|
||
fi
|
||
done
|
||
|
||
systemctl daemon-reload
|
||
print_success "Configuration systemd nettoyée pour compatibilité conteneur"
|
||
mark_step_done "$step_name"
|
||
return 0
|
||
fi
|
||
|
||
# Configuration pour système physique/VM
|
||
local services=(ssh sshd fail2ban chrony)
|
||
local hardened=0
|
||
|
||
for service in "${services[@]}"; do
|
||
local unit="${service}.service"
|
||
|
||
# Vérifier si le service existe et est actif ou enabled
|
||
systemctl list-unit-files | grep -q "^${unit}" || continue
|
||
|
||
if ! systemctl is-enabled "$unit" > /dev/null 2>&1 && \
|
||
! systemctl is-active "$unit" > /dev/null 2>&1; then
|
||
print_info "Service $unit non actif - ignoré"
|
||
continue
|
||
fi
|
||
|
||
mkdir -p "/etc/systemd/system/${unit}.d"
|
||
|
||
# Configuration de sécurité adaptée selon le service
|
||
if [[ "$service" == "ssh" || "$service" == "sshd" ]]; then
|
||
# Configuration plus permissive pour SSH
|
||
cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF
|
||
[Service]
|
||
NoNewPrivileges=yes
|
||
PrivateTmp=yes
|
||
ProtectSystem=full
|
||
ProtectHome=read-only
|
||
ProtectKernelTunables=yes
|
||
ProtectKernelModules=yes
|
||
ProtectControlGroups=yes
|
||
RestrictRealtime=yes
|
||
EOF
|
||
else
|
||
# Configuration standard pour les autres services
|
||
cat > "/etc/systemd/system/${unit}.d/security.conf" << EOF
|
||
[Service]
|
||
NoNewPrivileges=yes
|
||
PrivateTmp=yes
|
||
ProtectSystem=strict
|
||
ProtectHome=yes
|
||
ReadWritePaths=/var/log /var/lib/${service} /run/${service}
|
||
ProtectKernelTunables=yes
|
||
ProtectKernelModules=yes
|
||
ProtectControlGroups=yes
|
||
RestrictRealtime=yes
|
||
RestrictSUIDSGID=yes
|
||
EOF
|
||
fi
|
||
|
||
hardened=$((hardened + 1))
|
||
print_info "Service $unit durci"
|
||
done
|
||
|
||
# Recharger la configuration systemd
|
||
systemctl daemon-reload
|
||
|
||
# Vérifier que les services critiques démarrent toujours
|
||
print_info "Vérification des services critiques..."
|
||
for service in ssh sshd; do
|
||
local unit="${service}.service"
|
||
if systemctl list-unit-files | grep -q "^${unit}"; then
|
||
if systemctl is-active --quiet "$unit" 2>/dev/null; then
|
||
systemctl restart "$unit" 2>&1 | tee -a "$LOG_FILE" || {
|
||
print_error "Échec redémarrage $unit - restauration configuration"
|
||
rm -rf "/etc/systemd/system/${unit}.d"
|
||
systemctl daemon-reload
|
||
systemctl restart "$unit"
|
||
}
|
||
fi
|
||
fi
|
||
done
|
||
|
||
print_success "$hardened service(s) systemd durci(s)"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 25: Configuration PAM avancée
|
||
configure_advanced_pam() {
|
||
local step_name="configure_advanced_pam"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Configuration PAM avancée"
|
||
|
||
backup_file "/etc/pam.d/common-password"
|
||
|
||
# S'assurer sha512 et rounds
|
||
grep -q "pam_unix.so" /etc/pam.d/common-password && {
|
||
grep -q "sha512" /etc/pam.d/common-password || \
|
||
sed -i 's/pam_unix.so.*/& sha512/' /etc/pam.d/common-password
|
||
|
||
grep -q "rounds=" /etc/pam.d/common-password || \
|
||
sed -i 's/pam_unix.so.*/& rounds=500000/' /etc/pam.d/common-password
|
||
}
|
||
|
||
# Appliquer expiration mots de passe
|
||
print_info "Application expiration mots de passe aux utilisateurs..."
|
||
while IFS=: read -r user _ uid _ _ _ shell; do
|
||
[[ "$uid" -ge 1000 || "$user" == "root" ]] && \
|
||
[[ -n "$shell" ]] && \
|
||
[[ "$shell" != *"nologin"* ]] && \
|
||
[[ "$shell" != *"false"* ]] && {
|
||
chage -M 90 "$user" 2>/dev/null || true
|
||
chage -d 0 "$user" 2>/dev/null || true
|
||
print_info " → $user: Expiration 90 jours configurée"
|
||
}
|
||
done < /etc/passwd
|
||
|
||
print_success "Configuration PAM avancée appliquée"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 26: Vérification partitions
|
||
check_partition_layout() {
|
||
local step_name="check_partition_layout"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Vérification disposition partitions"
|
||
|
||
local partitions=(/home /tmp /var /var/log /var/log/audit)
|
||
local warnings=()
|
||
|
||
for partition in "${partitions[@]}"; do
|
||
mount | grep -q " on ${partition} " && {
|
||
local device=$(mount | grep " on ${partition} " | awk '{print $1}')
|
||
print_info " ✓ $partition: Partition séparée ($device)"
|
||
} || {
|
||
warnings+=("$partition")
|
||
print_warning " ⚠ $partition: Non monté séparément"
|
||
}
|
||
done
|
||
|
||
[[ ${#warnings[@]} -eq 0 ]] && \
|
||
print_success "Toutes les partitions critiques séparées" || {
|
||
print_warning "${#warnings[@]} partition(s) non séparée(s)"
|
||
detect_container && \
|
||
print_info "Conteneur: partitions gérées par l'hôte"
|
||
}
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 27: Vérification fichiers noyau
|
||
check_vmlinuz() {
|
||
local step_name="check_vmlinuz"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Vérification fichiers noyau"
|
||
|
||
local files=(/vmlinuz /boot/vmlinuz "/boot/vmlinuz-$(uname -r)" "/boot/initrd.img-$(uname -r)")
|
||
local found=0
|
||
|
||
for kfile in "${files[@]}"; do
|
||
[[ -f "$kfile" ]] && {
|
||
found=$((found + 1))
|
||
print_info " ✓ $kfile ($(du -h "$kfile" | cut -f1))"
|
||
}
|
||
done
|
||
|
||
[[ $found -eq 0 ]] && {
|
||
print_warning "Aucun fichier noyau trouvé"
|
||
detect_container && \
|
||
print_info "Conteneur: noyau géré par l'hôte" || {
|
||
print_error "Système physique: fichiers noyau manquants!"
|
||
apt-get install --reinstall "linux-image-$(uname -r)" 2>/dev/null || \
|
||
print_warning "Échec réinstallation noyau"
|
||
}
|
||
} || print_success "$found fichier(s) noyau trouvé(s)"
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 28: Exécution chkrootkit
|
||
run_chkrootkit() {
|
||
local step_name="run_chkrootkit"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Exécution de chkrootkit"
|
||
|
||
print_info "Scan rootkit en cours..."
|
||
chkrootkit > "$BACKUP_DIR/chkrootkit_report.log" 2>&1 || \
|
||
print_warning "Chkrootkit a détecté des avertissements (voir $BACKUP_DIR/chkrootkit_report.log)"
|
||
|
||
print_success "Scan chkrootkit terminé"
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 29: Nettoyage port SSH 22 temporaire
|
||
prepare_ssh_cleanup() {
|
||
local step_name="prepare_ssh_cleanup"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Préparation nettoyage port SSH 22"
|
||
|
||
print_warning "Port SSH 22 toujours actif (temporaire)"
|
||
print_info "Après avoir testé SSH sur le port $NEW_SSH_PORT:"
|
||
print_info "Exécutez: $0 --cleanup-ssh"
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ÉTAPE 30: Audit Lynis final
|
||
run_lynis_audit() {
|
||
local step_name="run_lynis_audit"
|
||
check_step_done "$step_name" && { skip_step "$step_name"; return 0; }
|
||
|
||
print_step "Exécution de l'audit Lynis"
|
||
|
||
print_info "Audit système complet en cours (peut prendre plusieurs minutes)..."
|
||
|
||
# Exécuter Lynis et sauvegarder le rapport
|
||
lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1
|
||
|
||
# Extraire le score de durcissement
|
||
local score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1)
|
||
local max_score=100 # Score maximum standard Lynis
|
||
|
||
# Si on ne trouve pas le score dans le rapport, utiliser une valeur par défaut
|
||
[[ -z "$score" ]] && score=0
|
||
|
||
# Sauvegarder le score pour le résumé
|
||
echo "$score" > "/tmp/lynis_score.txt"
|
||
echo "$max_score" > "/tmp/lynis_max_score.txt"
|
||
|
||
# Calculer le pourcentage pour la barre de progression
|
||
local percentage=$((score * 100 / max_score))
|
||
local bar_length=50 # Longueur de la barre en caractères
|
||
local filled_length=$((percentage * bar_length / 100))
|
||
local empty_length=$((bar_length - filled_length))
|
||
|
||
# Créer la barre de progression
|
||
local bar="["
|
||
for ((i=0; i<filled_length; i++)); do
|
||
bar+="█"
|
||
done
|
||
for ((i=0; i<empty_length; i++)); do
|
||
bar+="░"
|
||
done
|
||
bar+="]"
|
||
|
||
# Couleur selon le score
|
||
local color=$RED
|
||
[[ $score -ge 80 ]] && color=$GREEN
|
||
[[ $score -ge 60 && $score -lt 80 ]] && color=$YELLOW
|
||
|
||
print_success "Audit Lynis terminé"
|
||
|
||
# Afficher le score avec la barre de progression
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " SCORE LYNIS FINAL"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo -e "Score de durcissement: ${color}${bar} ${score}/${max_score}${NC}"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
print_info "Rapport complet: $SECURITY_REPORT"
|
||
|
||
# Extraire et afficher les statistiques
|
||
echo ""
|
||
print_info "Statistiques de l'audit:"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
local warnings=$(grep -c "Warning" "$SECURITY_REPORT" 2>/dev/null || echo "0")
|
||
local suggestions=$(grep -c "Suggestion" "$SECURITY_REPORT" 2>/dev/null || echo "0")
|
||
local tests=$(grep -c "Test" "$SECURITY_REPORT" 2>/dev/null || echo "0")
|
||
|
||
echo " • Tests effectués: $tests"
|
||
echo " • Avertissements: $warnings"
|
||
echo " • Suggestions: $suggestions"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
# Afficher les principales suggestions si score < 80
|
||
if [[ $score -lt 80 ]]; then
|
||
echo ""
|
||
print_info "Top 5 des suggestions prioritaires:"
|
||
echo "┌──────────────────────────────────────────────────────┐"
|
||
grep "Suggestion" "$SECURITY_REPORT" | head -5 | nl -w1 -s'. ' | while read -r line; do
|
||
echo "│ ${line}"
|
||
done
|
||
echo "└──────────────────────────────────────────────────────┘"
|
||
fi
|
||
|
||
# Afficher les warnings critiques
|
||
echo ""
|
||
if [[ $warnings -gt 0 ]]; then
|
||
print_warning "⚠ Avertissements critiques détectés:"
|
||
echo "┌──────────────────────────────────────────────────────┐"
|
||
grep "Warning" "$SECURITY_REPORT" | head -3 | nl -w1 -s'. ' | while read -r line; do
|
||
echo "│ ${line}"
|
||
done
|
||
echo "└──────────────────────────────────────────────────────┘"
|
||
fi
|
||
|
||
echo ""
|
||
print_info "Pour voir le rapport complet: cat $SECURITY_REPORT"
|
||
print_info "Pour voir les suggestions: grep Suggestion $SECURITY_REPORT"
|
||
|
||
mark_step_done "$step_name"
|
||
}
|
||
|
||
# ==============================================================================
|
||
# FONCTION NETTOYAGE SSH
|
||
# ==============================================================================
|
||
|
||
cleanup_ssh_port() {
|
||
print_step "Nettoyage définitif port SSH 22"
|
||
|
||
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
|
||
echo -e "${RED}║ ⚠ ATTENTION CRITIQUE ⚠ ║${NC}"
|
||
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
echo "Cette action va supprimer définitivement l'accès SSH sur le port 22."
|
||
echo "Assurez-vous d'avoir testé SSH sur le port $NEW_SSH_PORT."
|
||
echo ""
|
||
echo -n "Confirmez-vous avoir testé avec succès SSH:$NEW_SSH_PORT ? (oui/non): "
|
||
|
||
read -r confirmation
|
||
|
||
[[ "$confirmation" != "oui" ]] && {
|
||
print_error "Annulation. Testez d'abord: ssh -p $NEW_SSH_PORT user@$(hostname -I | awk '{print $1}')"
|
||
return 1
|
||
}
|
||
|
||
backup_file "/etc/ssh/sshd_config"
|
||
sed -i '/^Port 22$/d' /etc/ssh/sshd_config
|
||
ufw delete allow 22/tcp 2>/dev/null || true
|
||
|
||
sshd -t && {
|
||
systemctl restart sshd
|
||
print_success "Port SSH 22 supprimé"
|
||
print_info "SSH disponible uniquement sur le port $NEW_SSH_PORT"
|
||
} || {
|
||
print_error "Erreur configuration - restauration"
|
||
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
|
||
systemctl restart sshd
|
||
return 1
|
||
}
|
||
}
|
||
|
||
# ==============================================================================
|
||
# FONCTIONS PRINCIPALES
|
||
# ==============================================================================
|
||
|
||
check_requirements() {
|
||
print_info "Vérification des prérequis..."
|
||
|
||
[[ $EUID -ne 0 ]] && {
|
||
print_error "Ce script doit être exécuté en tant que root"
|
||
exit 1
|
||
}
|
||
|
||
[[ ! -f /etc/debian_version ]] && \
|
||
print_warning "Système non Debian/Ubuntu - compatibilité limitée"
|
||
|
||
mkdir -p "$BACKUP_DIR"
|
||
touch "$LOG_FILE" "$STATUS_FILE" 2>/dev/null || {
|
||
print_error "Impossible de créer les fichiers de log"
|
||
exit 1
|
||
}
|
||
|
||
print_success "Prérequis vérifiés"
|
||
}
|
||
|
||
print_header() {
|
||
clear
|
||
|
||
echo -e "${CYAN}"
|
||
echo "╔══════════════════════════════════════════════════════════════════╗"
|
||
echo "║ SCRIPT DE DURCISSEMENT SYSTÈME OPTIMISÉ v6.0 ║"
|
||
echo "║ Sécurité Debian/Ubuntu ║"
|
||
echo "╚══════════════════════════════════════════════════════════════════╝"
|
||
echo -e "${NC}"
|
||
|
||
echo -e "${BLUE}Informations système:${NC}"
|
||
echo " • Distribution: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"')"
|
||
echo " • Noyau: $(uname -r)"
|
||
echo " • Hostname: $(hostname)"
|
||
echo " • Type: $(detect_container && echo "Conteneur/LXC" || echo "Hôte physique/VM")"
|
||
echo ""
|
||
echo -e "${BLUE}Configuration:${NC}"
|
||
echo " • Port SSH nouveau: $NEW_SSH_PORT"
|
||
echo " • Fichier log: $LOG_FILE"
|
||
echo " • Sauvegardes: $BACKUP_DIR"
|
||
echo " • Rapport final: $SECURITY_REPORT"
|
||
echo ""
|
||
|
||
echo -e "${YELLOW}⚠ Ce script va modifier profondément la configuration système ⚠${NC}"
|
||
echo ""
|
||
|
||
read -p "Appuyez sur Entrée pour continuer ou Ctrl+C pour annuler..."
|
||
}
|
||
|
||
print_summary() {
|
||
echo -e "\n${GREEN}"
|
||
echo "╔══════════════════════════════════════════════════════════════════╗"
|
||
echo "║ DURCISSEMENT SYSTÈME TERMINÉ AVEC SUCCÈS ║"
|
||
echo "╚══════════════════════════════════════════════════════════════════╝"
|
||
echo -e "${NC}"
|
||
|
||
# Récupérer le score Lynis
|
||
local lynis_score="N/A"
|
||
local lynis_max_score=100
|
||
|
||
if [[ -f "/tmp/lynis_score.txt" ]]; then
|
||
lynis_score=$(cat /tmp/lynis_score.txt)
|
||
[[ -f "/tmp/lynis_max_score.txt" ]] && lynis_max_score=$(cat /tmp/lynis_max_score.txt)
|
||
elif [[ -f "$SECURITY_REPORT" ]]; then
|
||
lynis_score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1)
|
||
[[ -z "$lynis_score" ]] && lynis_score="N/A"
|
||
fi
|
||
|
||
# Afficher le score avec barre de progression dans le résumé
|
||
if [[ "$lynis_score" != "N/A" ]]; then
|
||
# Calcul de la barre de progression
|
||
local percentage=$((lynis_score * 100 / lynis_max_score))
|
||
local bar_length=25 # Barre plus courte pour le résumé
|
||
local filled_length=$((percentage * bar_length / 100))
|
||
local empty_length=$((bar_length - filled_length))
|
||
|
||
local bar="["
|
||
for ((i=0; i<filled_length; i++)); do
|
||
bar+="█"
|
||
done
|
||
for ((i=0; i<empty_length; i++)); do
|
||
bar+="░"
|
||
done
|
||
bar+="]"
|
||
|
||
# Déterminer la couleur du score
|
||
local score_color=$RED
|
||
[[ $lynis_score -ge 80 ]] && score_color=$GREEN
|
||
[[ $lynis_score -ge 60 && $lynis_score -lt 80 ]] && score_color=$YELLOW
|
||
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " SCORE LYNIS FINAL"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo -e "Score de durcissement: ${score_color}${bar} ${lynis_score}/${lynis_max_score}${NC}"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
fi
|
||
|
||
echo -e "\n${MAGENTA}════════════════════ RÉSUMÉ FINAL ════════════════════${NC}\n"
|
||
|
||
# Déterminer la couleur du score pour le résumé
|
||
local score_color=$RED
|
||
if [[ "$lynis_score" != "N/A" ]]; then
|
||
[[ $lynis_score -ge 80 ]] && score_color=$GREEN
|
||
[[ $lynis_score -ge 60 && $lynis_score -lt 80 ]] && score_color=$YELLOW
|
||
|
||
echo -e "${score_color}╔══════════════════════════════════════════════════════════════╗${NC}"
|
||
echo -e "${score_color}║ 🏆 SCORE DE SÉCURITÉ FINAL ║${NC}"
|
||
echo -e "${score_color}║ ║${NC}"
|
||
printf "${score_color}║ %3s / 100 points ║${NC}\n" "$lynis_score"
|
||
|
||
if [[ $lynis_score -ge 80 ]]; then
|
||
echo -e "${score_color}║ ★★★★★ Excellent ║${NC}"
|
||
elif [[ $lynis_score -ge 60 ]]; then
|
||
echo -e "${score_color}║ ★★★☆☆ Bon ║${NC}"
|
||
elif [[ $lynis_score -ge 40 ]]; then
|
||
echo -e "${score_color}║ ★★☆☆☆ Moyen ║${NC}"
|
||
else
|
||
echo -e "${score_color}║ ★☆☆☆☆ À améliorer ║${NC}"
|
||
fi
|
||
echo -e "${score_color}║ ║${NC}"
|
||
echo -e "${score_color}╚══════════════════════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
fi
|
||
|
||
echo -e "${RED}🔐 ACTIONS CRITIQUES IMMÉDIATES:${NC}"
|
||
echo -e " 1. ${RED}TESTEZ SSH MAINTENANT:${NC}"
|
||
echo " → ssh -p $NEW_SSH_PORT $(whoami)@$(hostname -I | awk '{print $1}')"
|
||
echo -e " → ${RED}NE FERMEZ PAS CETTE SESSION AVANT VALIDATION !${NC}"
|
||
echo ""
|
||
echo " 2. Une fois SSH:$NEW_SSH_PORT validé:"
|
||
echo " → sudo $0 --cleanup-ssh"
|
||
echo ""
|
||
|
||
echo -e "${YELLOW}📊 SERVICES CONFIGURÉS:${NC}"
|
||
echo " • SSH: Ports 22 (temporaire) + $NEW_SSH_PORT (permanent)"
|
||
echo " • UFW: $(systemctl is-active ufw 2>/dev/null || echo "désactivé (conteneur)")"
|
||
echo " • Fail2ban: $(systemctl is-active fail2ban 2>/dev/null || echo "inactif")"
|
||
echo " • AIDE: Base initialisée + vérification hebdomadaire"
|
||
echo " • ClamAV: $(systemctl is-active clamav-freshclam 2>/dev/null || echo "inactif")"
|
||
echo " • Chrony: $(systemctl is-active chrony 2>/dev/null || echo "inactif (conteneur)")"
|
||
echo ""
|
||
|
||
echo -e "${YELLOW}📁 FICHIERS GÉNÉRÉS:${NC}"
|
||
echo " • Log complet: $LOG_FILE"
|
||
echo " • Sauvegardes: $BACKUP_DIR"
|
||
echo " • Rapport Lynis: $SECURITY_REPORT"
|
||
echo " • Rapport chkrootkit: $BACKUP_DIR/chkrootkit_report.log"
|
||
echo ""
|
||
|
||
# Statistiques Lynis détaillées
|
||
if [[ -f "$SECURITY_REPORT" ]]; then
|
||
local warnings=$(grep -c "Warning" "$SECURITY_REPORT" 2>/dev/null || echo "0")
|
||
local suggestions=$(grep -c "Suggestion" "$SECURITY_REPORT" 2>/dev/null || echo "0")
|
||
|
||
echo -e "${YELLOW}📈 STATISTIQUES LYNIS:${NC}"
|
||
echo " • Avertissements: $warnings"
|
||
echo " • Suggestions d'amélioration: $suggestions"
|
||
echo ""
|
||
fi
|
||
|
||
detect_container && {
|
||
echo -e "${YELLOW}📦 SPÉCIFICITÉS CONTENEUR:${NC}"
|
||
echo " • Process Accounting: Désactivé (géré par hôte)"
|
||
echo " • UFW/Pare-feu: Géré par l'hôte Proxmox"
|
||
echo " • Modules noyau: Gérés par l'hôte"
|
||
echo " • Partitions: Gérées par Proxmox"
|
||
echo " • Chrony: Synchronisation horaire gérée par l'hôte"
|
||
echo ""
|
||
}
|
||
|
||
echo -e "${YELLOW}🔍 COMMANDES UTILES POST-INSTALLATION:${NC}"
|
||
echo " 1. État Fail2ban: sudo fail2ban-client status sshd"
|
||
echo " 2. Logs SSH: sudo tail -f /var/log/auth.log"
|
||
echo " 3. Ports ouverts: sudo ss -tlnp"
|
||
echo " 4. Règles UFW: sudo ufw status verbose"
|
||
echo " 5. Score Lynis: grep 'Hardening index' $SECURITY_REPORT"
|
||
echo " 6. Suggestions: grep 'Suggestion' $SECURITY_REPORT"
|
||
echo " 7. Rapport complet: cat $SECURITY_REPORT | less"
|
||
echo ""
|
||
|
||
# Interpréter le score
|
||
if [[ "$lynis_score" != "N/A" ]]; then
|
||
echo -e "${CYAN}💡 INTERPRÉTATION DU SCORE:${NC}"
|
||
if [[ $lynis_score -ge 80 ]]; then
|
||
echo " ✓ Excellente sécurité ! Votre système est bien durci."
|
||
echo " → Continuez à surveiller et à appliquer les mises à jour."
|
||
elif [[ $lynis_score -ge 60 ]]; then
|
||
echo " ✓ Bonne sécurité, mais des améliorations sont possibles."
|
||
echo " → Consultez les suggestions Lynis pour progresser."
|
||
elif [[ $lynis_score -ge 40 ]]; then
|
||
echo " ⚠ Sécurité moyenne, améliorations recommandées."
|
||
echo " → Examinez attentivement les avertissements et suggestions."
|
||
else
|
||
echo " ⚠ Score faible - plusieurs améliorations nécessaires."
|
||
echo " → Priorisez les warnings critiques de Lynis."
|
||
fi
|
||
echo ""
|
||
fi
|
||
|
||
echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}"
|
||
echo -e "${RED}⚠ TESTEZ SSH:$NEW_SSH_PORT IMMÉDIATEMENT AVANT TOUTE AUTRE ACTION ⚠${NC}"
|
||
echo -e "${RED}══════════════════════════════════════════════════════════════════${NC}"
|
||
|
||
# Nettoyage
|
||
rm -f /tmp/lynis_score.txt /tmp/lynis_max_score.txt 2>/dev/null || true
|
||
}
|
||
|
||
main() {
|
||
# Mode nettoyage SSH
|
||
[[ $# -eq 1 && "$1" == "--cleanup-ssh" ]] && {
|
||
cleanup_ssh_port
|
||
return 0
|
||
}
|
||
|
||
# Initialisation
|
||
check_requirements
|
||
print_header
|
||
|
||
log_message "==================================================" "START"
|
||
log_message "Démarrage durcissement système v6.0" "START"
|
||
log_message "Système: $(hostname)" "START"
|
||
log_message "==================================================" "START"
|
||
|
||
# EXÉCUTION DES ÉTAPES
|
||
install_security_tools
|
||
configure_process_accounting
|
||
configure_sysctl_security
|
||
configure_log_permissions
|
||
configure_pam_password_policy
|
||
configure_login_defs
|
||
configure_umask
|
||
configure_aide_sha512
|
||
initialize_aide_db
|
||
configure_clamav
|
||
configure_chrony
|
||
harden_ssh
|
||
configure_banners
|
||
configure_ufw_firewall
|
||
configure_fail2ban
|
||
remove_unneeded_packages
|
||
restrict_file_permissions
|
||
disable_risky_kernel_modules
|
||
configure_security_limits
|
||
verify_packages_integrity
|
||
configure_automatic_updates
|
||
configure_aide_cron
|
||
harden_smtp_banner
|
||
harden_systemd_services
|
||
configure_advanced_pam
|
||
check_partition_layout
|
||
check_vmlinuz
|
||
run_chkrootkit
|
||
prepare_ssh_cleanup
|
||
run_lynis_audit
|
||
|
||
# Résumé
|
||
print_summary
|
||
|
||
log_message "==================================================" "END"
|
||
log_message "Durcissement terminé - Durée: $((SECONDS / 60)) min" "END"
|
||
log_message "==================================================" "END"
|
||
}
|
||
|
||
# Gestion signaux
|
||
trap 'print_error "Script interrompu"; exit 130' INT TERM
|
||
|
||
# Point d'entrée
|
||
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@" |