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