1155 lines
47 KiB
Bash
1155 lines
47 KiB
Bash
#!/bin/bash
|
|
|
|
################################################################################
|
|
# Script: system_hardening_optimized.sh
|
|
# Version: 8.2
|
|
# 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
|
|
|
|
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"
|
|
|
|
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"
|
|
|
|
: "${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:=no}"
|
|
: "${AUTO_SKIP_DEBSUMS_CONTAINER:=yes}"
|
|
|
|
TOTAL_STEPS=33
|
|
CURRENT_STEP=1
|
|
|
|
FORCE_ALL=false
|
|
FORCE_STEPS=()
|
|
SKIP_STEPS=()
|
|
RESET_ALL=false
|
|
LIST_STEPS=false
|
|
SHOW_STATUS=false
|
|
UNATTENDED=false
|
|
|
|
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'
|
|
|
|
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"
|
|
)
|
|
|
|
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}[STEP ${CURRENT_STEP}/${TOTAL_STEPS}] $step_title${NC}"
|
|
log_message "Début étape $CURRENT_STEP: $step_title" "STEP"
|
|
CURRENT_STEP=$((CURRENT_STEP + 1))
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[OK]${NC} $1"
|
|
log_message "$1" "SUCCESS"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
log_message "$1" "WARNING"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERR]${NC} $1" >&2
|
|
log_message "$1" "ERROR"
|
|
}
|
|
|
|
print_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
log_message "$1" "INFO"
|
|
}
|
|
|
|
auto_confirm() {
|
|
[[ "$AUTO_YES" == "yes" ]] && return 0
|
|
[[ "$UNATTENDED" == true ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
check_step_done() {
|
|
local step_name="$1"
|
|
$FORCE_ALL && return 1
|
|
[[ " ${FORCE_STEPS[@]} " =~ " ${step_name} " ]] && { print_info "Forçage de l'étape: $step_name"; return 1; }
|
|
[[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]] && { print_info "Saut de l'étape: $step_name"; return 0; }
|
|
grep -q "^${step_name}$" "$STATUS_FILE" 2>/dev/null
|
|
}
|
|
|
|
mark_step_done() {
|
|
local step_name="$1"
|
|
[[ " ${SKIP_STEPS[@]} " =~ " ${step_name} " ]] && { print_info "Étape $step_name ignorée"; return 0; }
|
|
sed -i "/^${step_name}$/d" "$STATUS_FILE" 2>/dev/null || true
|
|
echo "$step_name" >> "$STATUS_FILE"
|
|
log_message "Étape '$step_name' 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() {
|
|
[[ -f /.dockerenv ]] && return 0
|
|
[[ -d /dev/lxc ]] && return 0
|
|
[[ -f /.lxcpid ]] && return 0
|
|
[[ -n "${container:-}" ]] && return 0
|
|
[[ -n "${DOCKER_HOST:-}" ]] && return 0
|
|
[[ -n "${KUBERNETES_SERVICE_HOST:-}" ]] && return 0
|
|
if [[ -f /proc/1/cgroup ]]; then
|
|
local cgroup_content
|
|
cgroup_content=$(cat /proc/1/cgroup 2>/dev/null || echo "")
|
|
echo "$cgroup_content" | grep -qE "(docker|lxc|kubepods|containerd|podman)" && return 0
|
|
fi
|
|
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") return 0 ;;
|
|
esac
|
|
fi
|
|
[[ -f /run/.containerenv ]] && return 0
|
|
log_message "Aucun conteneur détecté" "DETECT"
|
|
return 1
|
|
}
|
|
|
|
detect_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
|
|
}
|
|
|
|
detect_open_ports() {
|
|
local step_name="detect_open_ports"
|
|
[[ "$AUTO_SKIP_PORTS_DETECTION" == "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Détection des ports ouverts"
|
|
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
|
|
echo "22" > "$OPEN_PORTS_FILE"
|
|
fi
|
|
local port_count=$(wc -l < "$OPEN_PORTS_FILE" 2>/dev/null || echo 0)
|
|
print_success "$port_count port(s) ouvert(s) détecté(s)"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
is_port_open() {
|
|
grep -q "^${1}$" "$OPEN_PORTS_FILE" 2>/dev/null
|
|
}
|
|
|
|
get_ssh_port_to_use() {
|
|
detect_lxc && echo "22" || echo "$AUTO_SSH_PORT"
|
|
}
|
|
|
|
configure_banners() {
|
|
local step_name="configure_banners"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration des bannières légales"
|
|
cat > /etc/issue.net << 'EOF'
|
|
╔══════════════════════════════════════════════════════════════════╗
|
|
║ SYSTÈME SÉCURISÉ ║
|
|
║ Accès strictement réservé aux personnes autorisées. ║
|
|
║ Toutes les activités sont surveillées et enregistrées. ║
|
|
╚══════════════════════════════════════════════════════════════════╝
|
|
EOF
|
|
chmod 644 /etc/issue.net
|
|
cat > /etc/issue << 'EOF'
|
|
╔══════════════════════════════════════════════════════════════════╗
|
|
║ SYSTÈME SÉCURISÉ ║
|
|
║ Accès strictement réservé aux personnes autorisées. ║
|
|
╚══════════════════════════════════════════════════════════════════╝
|
|
EOF
|
|
chmod 644 /etc/issue
|
|
[[ -d /etc/update-motd.d ]] && chmod -x /etc/update-motd.d/* 2>/dev/null || true
|
|
print_success "Bannières légales configurées"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
_filter_available_packages() {
|
|
local available=()
|
|
for pkg in "$@"; do
|
|
if apt-cache show "$pkg" >/dev/null 2>&1; then
|
|
available+=("$pkg")
|
|
else
|
|
log_message "Paquet non disponible sur ce système, ignoré: $pkg" "WARNING"
|
|
fi
|
|
done
|
|
echo "${available[@]:-}"
|
|
}
|
|
|
|
install_security_tools() {
|
|
local step_name="install_security_tools"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Mise à jour système et installation outils de sécurité"
|
|
DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
|
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq
|
|
local candidate_packages=(
|
|
lynis aide aide-common ufw libpam-pwquality
|
|
apt-listchanges needrestart chrony
|
|
chkrootkit libpam-tmpdir debsums unattended-upgrades
|
|
)
|
|
[[ "$AUTO_ENABLE_FAIL2BAN" == "yes" ]] && candidate_packages+=(fail2ban)
|
|
[[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && candidate_packages+=(clamav clamav-daemon)
|
|
detect_container || candidate_packages+=(acct)
|
|
local installable
|
|
installable=$(_filter_available_packages "${candidate_packages[@]}")
|
|
if [[ -n "$installable" ]]; then
|
|
# shellcheck disable=SC2086
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $installable
|
|
fi
|
|
print_success "Système mis à jour et outils installés"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
change_root_password() {
|
|
local step_name="change_root_password"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Changement du mot de passe root"
|
|
if [[ "$AUTO_CHANGE_ROOT_PWD" != "yes" ]]; then
|
|
print_info "Changement mot de passe root désactivé (AUTO_CHANGE_ROOT_PWD=no)"
|
|
mark_step_done "$step_name"
|
|
return 0
|
|
fi
|
|
detect_container && { print_warning "Conteneur détecté - changement ignoré"; mark_step_done "$step_name"; return 0; }
|
|
local root_shell=$(grep "^root:" /etc/passwd | cut -d: -f7)
|
|
[[ "$root_shell" == "/usr/sbin/nologin" || "$root_shell" == "/bin/false" ]] && {
|
|
print_warning "Root a un shell non interactif - ignoré"
|
|
mark_step_done "$step_name"
|
|
return 0
|
|
}
|
|
local NEW_ROOT_PASSWORD
|
|
NEW_ROOT_PASSWORD=$(tr -dc 'A-Za-z0-9!@#$%^&*' < /dev/urandom | head -c 20)
|
|
local PWD_FILE="/root/root_password_$(date +%Y%m%d_%H%M%S).txt"
|
|
cat > "$PWD_FILE" << EOF
|
|
=== MOT DE PASSE ROOT - $(hostname) - $(date) ===
|
|
Mot de passe root: $NEW_ROOT_PASSWORD
|
|
CONSERVEZ CE FICHIER EN LIEU SÛR
|
|
=== FIN ===
|
|
EOF
|
|
chmod 600 "$PWD_FILE"
|
|
chown root:root "$PWD_FILE"
|
|
if echo "root:$NEW_ROOT_PASSWORD" | chpasswd -c SHA512 2>/dev/null; then
|
|
chage -M 90 root 2>/dev/null || true
|
|
print_success "Mot de passe root changé - sauvegardé dans $PWD_FILE"
|
|
else
|
|
rm -f "$PWD_FILE"
|
|
print_error "Échec changement mot de passe root"
|
|
return 1
|
|
fi
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_process_accounting() {
|
|
local step_name="configure_process_accounting"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration du Process Accounting"
|
|
if detect_container; then
|
|
print_warning "Conteneur détecté - Process Accounting désactivé"
|
|
for service in acct.service psacct.service; do
|
|
systemctl list-unit-files | grep -q "^${service}" && {
|
|
systemctl stop "$service" 2>/dev/null || true
|
|
systemctl disable "$service" 2>/dev/null || true
|
|
systemctl mask "$service" 2>/dev/null || true
|
|
}
|
|
done
|
|
else
|
|
systemctl enable acct.service 2>/dev/null && \
|
|
systemctl start acct.service 2>/dev/null && \
|
|
print_success "Process Accounting activé" || \
|
|
print_warning "Process Accounting non disponible"
|
|
fi
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_sysctl_security() {
|
|
local step_name="configure_sysctl_security"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Durcissement des paramètres noyau (sysctl)"
|
|
cat > /etc/sysctl.d/99-security-hardening.conf << 'EOF'
|
|
net.ipv4.conf.all.accept_source_route = 0
|
|
net.ipv4.conf.all.accept_redirects = 0
|
|
net.ipv4.conf.all.send_redirects = 0
|
|
net.ipv4.conf.all.log_martians = 1
|
|
net.ipv4.conf.all.rp_filter = 1
|
|
net.ipv4.conf.default.accept_source_route = 0
|
|
net.ipv4.conf.default.accept_redirects = 0
|
|
net.ipv4.conf.default.log_martians = 1
|
|
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
|
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
|
net.ipv4.tcp_syncookies = 1
|
|
net.ipv4.tcp_max_syn_backlog = 2048
|
|
net.ipv4.tcp_synack_retries = 2
|
|
net.ipv4.tcp_syn_retries = 5
|
|
net.ipv6.conf.all.accept_redirects = 0
|
|
net.ipv6.conf.all.accept_source_route = 0
|
|
net.ipv6.conf.default.accept_redirects = 0
|
|
net.ipv6.conf.default.accept_source_route = 0
|
|
kernel.randomize_va_space = 2
|
|
kernel.kptr_restrict = 2
|
|
kernel.dmesg_restrict = 1
|
|
kernel.yama.ptrace_scope = 1
|
|
kernel.unprivileged_bpf_disabled = 1
|
|
fs.suid_dumpable = 0
|
|
fs.protected_fifos = 2
|
|
fs.protected_regular = 2
|
|
fs.protected_symlinks = 1
|
|
fs.protected_hardlinks = 1
|
|
EOF
|
|
sysctl -p /etc/sysctl.d/99-security-hardening.conf 2>/dev/null || print_warning "Certains paramètres sysctl ignorés"
|
|
print_success "Paramètres sysctl appliqués"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_log_permissions() {
|
|
local step_name="configure_log_permissions"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration des permissions des fichiers de log"
|
|
chmod 750 /var/log 2>/dev/null || true
|
|
chown root:adm /var/log 2>/dev/null || true
|
|
find /var/log -type f -exec chmod 640 {} \; 2>/dev/null || true
|
|
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
|
|
done
|
|
[[ -d /var/log/audit ]] && { chmod 700 /var/log/audit 2>/dev/null || true; find /var/log/audit -type f -exec chmod 600 {} \; 2>/dev/null || true; }
|
|
command -v journalctl > /dev/null 2>&1 && { chmod 2750 /var/log/journal 2>/dev/null || true; chown root:systemd-journal /var/log/journal 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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration de la politique de mots de passe PAM"
|
|
backup_file "/etc/security/pwquality.conf"
|
|
cat > /etc/security/pwquality.conf << 'EOF'
|
|
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"
|
|
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
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration des paramètres de connexion (login.defs)"
|
|
backup_file "/etc/login.defs"
|
|
update_config_value "/etc/login.defs" "PASS_MAX_DAYS" "$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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration de l'umask par défaut"
|
|
for file in /etc/profile /etc/bash.bashrc /etc/zsh/zshrc; do
|
|
[[ -f "$file" ]] && { backup_file "$file"; sed -i '/^umask/d' "$file"; add_unique_line "umask $AUTO_UMASK" "$file"; }
|
|
done
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_ENABLE_AIDE" != "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Configuration AIDE pour SHA512"
|
|
backup_file "/etc/aide/aide.conf"
|
|
cat > /etc/aide/aide.conf << 'EOF'
|
|
database=file:/var/lib/aide/aide.db.gz
|
|
database_out=file:/var/lib/aide/aide.db.new.gz
|
|
gzip_dbout=yes
|
|
|
|
Normal = sha512
|
|
Large = sha512+ftype
|
|
Everything = sha512+ftype+p+i+n+u+g+s+m+c+acl+selinux+xattrs
|
|
|
|
/etc Everything
|
|
/bin Large
|
|
/sbin Large
|
|
/usr/bin Large
|
|
/usr/sbin Large
|
|
/boot Large
|
|
/lib Large
|
|
/lib64 Large
|
|
/root Large
|
|
|
|
!/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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_ENABLE_AIDE" != "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Initialisation de la base de données AIDE"
|
|
|
|
# Corriger la config AIDE si elle utilise encore les macros @@{} non supportées
|
|
if grep -q '@@{' /etc/aide/aide.conf 2>/dev/null; then
|
|
print_warning "Config AIDE avec macros non supportées détectée - correction automatique"
|
|
cat > /etc/aide/aide.conf << 'EOF'
|
|
database=file:/var/lib/aide/aide.db.gz
|
|
database_out=file:/var/lib/aide/aide.db.new.gz
|
|
gzip_dbout=yes
|
|
|
|
Normal = sha512
|
|
Large = sha512+ftype
|
|
Everything = sha512+ftype+p+i+n+u+g+s+m+c+acl+selinux+xattrs
|
|
|
|
/etc Everything
|
|
/bin Large
|
|
/sbin Large
|
|
/usr/bin Large
|
|
/usr/sbin Large
|
|
/boot Large
|
|
/lib Large
|
|
/lib64 Large
|
|
/root Large
|
|
|
|
!/proc
|
|
!/sys
|
|
!/tmp
|
|
!/var/tmp
|
|
!/var/run
|
|
!/var/log
|
|
!/dev
|
|
EOF
|
|
fi
|
|
|
|
if aide --init --config /etc/aide/aide.conf 2>&1 | tee -a "$LOG_FILE"; then
|
|
[[ -f /var/lib/aide/aide.db.new.gz ]] && 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 "Échec initialisation AIDE - étape ignorée, durcissement continue"
|
|
fi
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_clamav() {
|
|
local step_name="configure_clamav"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_ENABLE_CLAMAV" != "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Configuration de ClamAV"
|
|
mkdir -p /var/log/clamav /var/lib/clamav /run/clamav
|
|
id -u clamav >/dev/null 2>&1 || useradd -r -M -d /var/lib/clamav -s /bin/false -c "Clam AntiVirus" clamav 2>/dev/null || true
|
|
chown -R clamav:clamav /var/log/clamav /var/lib/clamav /run/clamav 2>/dev/null || true
|
|
touch /var/log/clamav/freshclam.log /var/log/clamav/clamav.log 2>/dev/null || true
|
|
chown clamav:clamav /var/log/clamav/freshclam.log /var/log/clamav/clamav.log 2>/dev/null || true
|
|
systemctl stop clamav-freshclam clamav-daemon 2>/dev/null || true
|
|
sleep 2
|
|
su - clamav -s /bin/bash -c "freshclam --quiet" 2>&1 | tee -a "$LOG_FILE" || \
|
|
freshclam --user=clamav --quiet 2>&1 | tee -a "$LOG_FILE" || \
|
|
print_warning "Mise à jour ClamAV échouée"
|
|
systemctl enable clamav-freshclam clamav-daemon 2>/dev/null || true
|
|
systemctl start clamav-freshclam 2>/dev/null && print_success "freshclam démarré" || print_warning "freshclam non démarré"
|
|
sleep 3
|
|
systemctl start clamav-daemon 2>/dev/null && print_success "clamav-daemon démarré" || print_warning "clamav-daemon non démarré"
|
|
print_success "ClamAV configuré"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_chrony() {
|
|
local step_name="configure_chrony"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration de la synchronisation horaire"
|
|
detect_container && { print_warning "Conteneur détecté - NTP géré par l'hôte"; mark_step_done "$step_name"; return 0; }
|
|
backup_file "/etc/chrony/chrony.conf"
|
|
cat > /etc/chrony/chrony.conf << EOF
|
|
pool 2.debian.pool.ntp.org iburst
|
|
server 0.fr.pool.ntp.org iburst
|
|
server 1.fr.pool.ntp.org iburst
|
|
server 2.fr.pool.ntp.org iburst
|
|
server 3.fr.pool.ntp.org iburst
|
|
driftfile /var/lib/chrony/chrony.drift
|
|
logdir /var/log/chrony
|
|
makestep 1.0 3
|
|
rtcsync
|
|
allow 127.0.0.1
|
|
deny all
|
|
local stratum 10
|
|
EOF
|
|
timedatectl set-timezone "$AUTO_TIMEZONE" 2>/dev/null || print_warning "Impossible de définir le fuseau horaire $AUTO_TIMEZONE"
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Durcissement du service SSH"
|
|
backup_file "/etc/ssh/sshd_config"
|
|
local sshd_config="/etc/ssh/sshd_config"
|
|
local ssh_port="22"
|
|
detect_lxc || { ssh_port="$AUTO_SSH_PORT"; [[ "$ssh_port" =~ ^[0-9]+$ ]] && [ "$ssh_port" -ge 1 ] && [ "$ssh_port" -le 65535 ] || ssh_port="22"; }
|
|
print_info "Configuration SSH avec le port: $ssh_port"
|
|
local temp_config
|
|
temp_config=$(mktemp)
|
|
{
|
|
echo "# Configuration SSH sécurisée - $(date)"
|
|
if [[ "$ssh_port" != "22" ]] && ! detect_lxc; then
|
|
echo "Port 22"
|
|
echo "Port $ssh_port"
|
|
else
|
|
echo "Port $ssh_port"
|
|
fi
|
|
echo "ListenAddress 0.0.0.0"
|
|
echo "ListenAddress ::"
|
|
[[ "$AUTO_DISABLE_ROOT_LOGIN" == "yes" ]] && echo "PermitRootLogin no" || echo "PermitRootLogin prohibit-password"
|
|
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 "Protocol 2"
|
|
echo "StrictModes yes"
|
|
echo "UseDNS no"
|
|
echo "IgnoreRhosts yes"
|
|
echo "HostbasedAuthentication no"
|
|
echo "PrintLastLog yes"
|
|
echo "X11Forwarding no"
|
|
echo "AllowAgentForwarding no"
|
|
echo "AllowTcpForwarding no"
|
|
echo "TCPKeepAlive no"
|
|
echo "Compression no"
|
|
echo "LogLevel VERBOSE"
|
|
echo "Banner /etc/issue.net"
|
|
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"
|
|
} > "$temp_config"
|
|
if ! sshd -t -f "$temp_config" 2>&1; then
|
|
print_warning "Configuration étendue invalide, passage en mode minimal"
|
|
cat > "$temp_config" << 'EOF'
|
|
Port 22
|
|
Protocol 2
|
|
PermitRootLogin prohibit-password
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
PermitEmptyPasswords no
|
|
X11Forwarding no
|
|
AllowTcpForwarding no
|
|
AllowAgentForwarding no
|
|
UsePAM yes
|
|
PrintLastLog yes
|
|
TCPKeepAlive no
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
EOF
|
|
if ! sshd -t -f "$temp_config" 2>&1; then
|
|
print_error "Configuration SSH minimale invalide"
|
|
rm -f "$temp_config"
|
|
return 1
|
|
fi
|
|
fi
|
|
cp "$temp_config" "$sshd_config"
|
|
chmod 600 "$sshd_config"
|
|
chown root:root "$sshd_config"
|
|
rm -f "$temp_config"
|
|
if sshd -t -f "$sshd_config" 2>&1 | tee -a "$LOG_FILE"; then
|
|
systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null && \
|
|
print_success "SSH durci avec succès (port $ssh_port)" || {
|
|
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
|
|
}
|
|
else
|
|
print_error "Configuration SSH invalide"
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_ENABLE_UFW" != "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Configuration des règles de pare-feu"
|
|
if detect_container; then
|
|
print_warning "Conteneur détecté - pare-feu géré par l'hôte"
|
|
command -v ufw > /dev/null 2>&1 && { ufw --force disable 2>/dev/null || true; systemctl stop ufw 2>/dev/null || true; systemctl disable ufw 2>/dev/null || true; }
|
|
mark_step_done "$step_name"; return 0
|
|
fi
|
|
ufw --force disable > /dev/null 2>&1 || true
|
|
ufw --force reset > /dev/null 2>&1
|
|
ufw default deny incoming
|
|
ufw default allow outgoing
|
|
local ssh_port
|
|
ssh_port=$(get_ssh_port_to_use)
|
|
ufw allow "${ssh_port}/tcp" comment 'SSH sécurisé'
|
|
[[ "$ssh_port" != "22" ]] && is_port_open "22" && ufw allow 22/tcp comment 'SSH temporaire'
|
|
ufw allow 22/tcp comment 'SSH'
|
|
ufw allow 53/udp comment 'DNS'
|
|
ufw allow 123/udp comment 'NTP'
|
|
if [[ -f "$OPEN_PORTS_FILE" ]]; then
|
|
while read -r port; do
|
|
[[ "$port" == "22" || "$port" == "$ssh_port" || "$port" == "53" || "$port" == "123" ]] && continue
|
|
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" ;;
|
|
3000) service_name="Grafana/WebApp" ;; 8080) service_name="Proxy/Web" ;; 8443) service_name="HTTPS Alt" ;;
|
|
*) ufw deny "${port}/tcp" comment "Port non standard $port (bloqué auto)"; continue ;;
|
|
esac
|
|
[[ -n "$service_name" ]] && ufw allow "${port}/tcp" comment "$service_name"
|
|
done < "$OPEN_PORTS_FILE"
|
|
fi
|
|
echo "y" | ufw --force enable > /dev/null 2>&1
|
|
ufw status | grep -q "Status: active" && print_success "Pare-feu UFW configuré et activé" || print_warning "UFW activé avec avertissements"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_fail2ban() {
|
|
local step_name="configure_fail2ban"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_ENABLE_FAIL2BAN" != "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Configuration de Fail2ban"
|
|
backup_file "/etc/fail2ban/jail.conf"
|
|
local ssh_port
|
|
ssh_port=$(get_ssh_port_to_use)
|
|
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
|
|
|
|
[recidive]
|
|
enabled = true
|
|
filter = recidive
|
|
logpath = /var/log/fail2ban.log
|
|
action = iptables-allports[name=recidive]
|
|
bantime = 1w
|
|
findtime = 1d
|
|
maxretry = 3
|
|
EOF
|
|
systemctl enable fail2ban 2>/dev/null || true
|
|
systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE" && print_success "Fail2ban configuré" || print_warning "Fail2ban démarré avec avertissements"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
remove_unneeded_packages() {
|
|
local step_name="remove_unneeded_packages"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Suppression des paquets inutiles"
|
|
for package in telnet rsh-client rsh-server netcat-openbsd netcat-traditional nis talk talkd; 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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Restriction des permissions des fichiers critiques"
|
|
chmod 644 /etc/passwd 2>/dev/null || true
|
|
chmod 600 /etc/shadow 2>/dev/null || true
|
|
chmod 644 /etc/group 2>/dev/null || true
|
|
chmod 600 /etc/gshadow 2>/dev/null || true
|
|
chmod 600 /etc/sudoers 2>/dev/null || true
|
|
chmod 750 /etc/sudoers.d 2>/dev/null || true
|
|
chmod 600 /boot/grub/grub.cfg 2>/dev/null || true
|
|
chmod 644 /etc/ssh/ssh_config 2>/dev/null || true
|
|
chmod 600 /etc/ssh/sshd_config 2>/dev/null || true
|
|
print_success "Permissions des fichiers critiques restreintes"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
disable_risky_kernel_modules() {
|
|
local step_name="disable_risky_kernel_modules"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
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
|
|
print_success "Modules noyau risqués désactivés"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_security_limits() {
|
|
local step_name="configure_security_limits"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Vérification de l'intégrité des paquets"
|
|
command -v debsums > /dev/null 2>&1 || { DEBIAN_FRONTEND=noninteractive apt-get install -y -qq debsums 2>/dev/null || { print_warning "Impossible d'installer debsums"; mark_step_done "$step_name"; return 0; }; }
|
|
local debsums_output
|
|
debsums_output=$(debsums -s 2>&1 | grep -v "Permission denied" || true)
|
|
if [[ -n "$debsums_output" ]]; then
|
|
echo "$debsums_output" | tee -a "$LOG_FILE"
|
|
print_warning "Certains paquets ont des erreurs d'intégrité"
|
|
else
|
|
print_success "Tous les paquets sont intacts"
|
|
fi
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_aide_cron() {
|
|
local step_name="configure_aide_cron"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_ENABLE_AIDE" != "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Configuration des vérifications AIDE planifiées"
|
|
cat > /etc/cron.daily/aide-check << 'EOF'
|
|
#!/bin/bash
|
|
LOGFILE="/var/log/aide-check-$(date +%Y%m%d).log"
|
|
command -v aide > /dev/null || exit 0
|
|
echo "=== Vérification AIDE $(date) ===" > "$LOGFILE"
|
|
/usr/bin/aide --check 2>&1 >> "$LOGFILE" && \
|
|
echo "AIDE: OK - $(date)" >> "$LOGFILE" || \
|
|
{ echo "AIDE: ALERTE - $(date)" >> "$LOGFILE"; echo "Alerte AIDE sur $(hostname)" | mail -s "[AIDE] Changements détectés" root 2>/dev/null || true; }
|
|
find /var/log -name "aide-check-*.log" -mtime +30 -delete 2>/dev/null || true
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Durcissement bannière SMTP"
|
|
if dpkg -l | grep -q "^ii.*postfix"; then
|
|
backup_file "/etc/postfix/main.cf"
|
|
postconf -e "smtpd_banner = \$myhostname ESMTP"
|
|
postconf -e "disable_vrfy_command = yes"
|
|
systemctl reload postfix 2>/dev/null || true
|
|
print_success "Bannière Postfix durcie"
|
|
else
|
|
print_info "Aucun serveur SMTP détecté"
|
|
fi
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
harden_systemd_services() {
|
|
local step_name="harden_systemd_services"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Durcissement services systemd"
|
|
command -v systemd-analyze > /dev/null || { print_warning "systemd-analyze non disponible"; mark_step_done "$step_name"; return 0; }
|
|
if detect_container; then
|
|
for service in ssh sshd fail2ban chrony; do
|
|
rm -rf "/etc/systemd/system/${service}.service.d" 2>/dev/null || true
|
|
done
|
|
systemctl daemon-reload
|
|
print_success "Configuration systemd nettoyée (conteneur)"
|
|
mark_step_done "$step_name"; return 0
|
|
fi
|
|
local hardened=0
|
|
for service in ssh sshd fail2ban chrony; do
|
|
local unit="${service}.service"
|
|
systemctl list-unit-files | grep -q "^${unit}" || continue
|
|
mkdir -p "/etc/systemd/system/${unit}.d"
|
|
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
|
|
hardened=$((hardened + 1))
|
|
done
|
|
systemctl daemon-reload
|
|
print_success "$hardened service(s) systemd durci(s)"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_advanced_pam() {
|
|
local step_name="configure_advanced_pam"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
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
|
|
}
|
|
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"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Vérification disposition partitions"
|
|
local warnings=0
|
|
for partition in /home /tmp /var /var/log /var/log/audit; do
|
|
mount | grep -q " on ${partition} " && print_info " ✓ $partition: Partition séparée" || { warnings=$((warnings+1)); 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)"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
check_vmlinuz() {
|
|
local step_name="check_vmlinuz"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Vérification fichiers noyau"
|
|
local found=0
|
|
for kfile in /vmlinuz /boot/vmlinuz "/boot/vmlinuz-$(uname -r)" "/boot/initrd.img-$(uname -r)"; do
|
|
[[ -f "$kfile" ]] && { found=$((found+1)); print_info " ✓ $kfile"; }
|
|
done
|
|
[[ $found -eq 0 ]] && print_warning "Aucun fichier noyau trouvé" || print_success "$found fichier(s) noyau trouvé(s)"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
run_chkrootkit() {
|
|
local step_name="run_chkrootkit"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Exécution de chkrootkit"
|
|
mkdir -p "$BACKUP_DIR"
|
|
chkrootkit > "$BACKUP_DIR/chkrootkit_report.log" 2>&1 || print_warning "Chkrootkit a détecté des avertissements"
|
|
print_success "Scan chkrootkit terminé - rapport: $BACKUP_DIR/chkrootkit_report.log"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
prepare_ssh_cleanup() {
|
|
local step_name="prepare_ssh_cleanup"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Préparation nettoyage port SSH 22"
|
|
local ssh_port
|
|
ssh_port=$(get_ssh_port_to_use)
|
|
[[ "$ssh_port" != "22" ]] && print_info "Après test du port $ssh_port: relancez avec --cleanup-ssh" || print_info "Port SSH principal: 22 - aucun nettoyage nécessaire"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
run_lynis_audit() {
|
|
local step_name="run_lynis_audit"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
[[ "$AUTO_SKIP_LYNIS" == "yes" ]] && { mark_step_done "$step_name"; return 0; }
|
|
print_step "Exécution de l'audit Lynis"
|
|
lynis audit system --quick --no-colors > "$SECURITY_REPORT" 2>&1
|
|
local score
|
|
score=$(grep -i "Hardening index" "$SECURITY_REPORT" | grep -oP '\d+' | head -1 || echo "0")
|
|
print_success "Audit Lynis terminé - Score: ${score}/100 - Rapport: $SECURITY_REPORT"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
configure_automatic_updates() {
|
|
local step_name="configure_automatic_updates"
|
|
check_step_done "$step_name" && { skip_step "${STEP_DESCRIPTIONS[$step_name]}"; return 0; }
|
|
print_step "Configuration des mises à jour automatiques"
|
|
dpkg -l | grep -q "^ii.*unattended-upgrades" || DEBIAN_FRONTEND=noninteractive apt-get install -y -qq unattended-upgrades apt-listchanges needrestart 2>/dev/null || true
|
|
backup_file "/etc/apt/apt.conf.d/50unattended-upgrades"
|
|
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
|
|
Unattended-Upgrade::Allowed-Origins {
|
|
"${distro_id}:${distro_codename}";
|
|
"${distro_id}:${distro_codename}-security";
|
|
"${distro_id}:${distro_codename}-updates";
|
|
};
|
|
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
|
Unattended-Upgrade::MinimalSteps "true";
|
|
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
Unattended-Upgrade::Automatic-Reboot "false";
|
|
Unattended-Upgrade::Mail "root";
|
|
Unattended-Upgrade::MailOnlyOnError "true";
|
|
EOF
|
|
cat > /etc/apt/apt.conf.d/10periodic << 'EOF'
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
APT::Periodic::AutocleanInterval "7";
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
APT::Periodic::Verbose "0";
|
|
EOF
|
|
systemctl enable unattended-upgrades 2>/dev/null || true
|
|
systemctl restart unattended-upgrades 2>/dev/null || systemctl start unattended-upgrades 2>/dev/null || true
|
|
print_success "Mises à jour automatiques configurées"
|
|
mark_step_done "$step_name"
|
|
}
|
|
|
|
cleanup_ssh_port() {
|
|
local ssh_port
|
|
ssh_port=$(get_ssh_port_to_use)
|
|
[[ "$ssh_port" == "22" ]] && { print_info "Port SSH principal déjà sur 22"; return 0; }
|
|
backup_file "/etc/ssh/sshd_config"
|
|
sed -i '/^Port 22$/d' /etc/ssh/sshd_config
|
|
command -v ufw > /dev/null 2>&1 && ufw status | grep -q "22/tcp" && ufw delete allow 22/tcp 2>/dev/null || true
|
|
sshd -t && { systemctl restart sshd 2>/dev/null; print_success "Port SSH 22 supprimé - SSH sur port $ssh_port uniquement"; } || {
|
|
print_error "Erreur configuration - restauration"
|
|
cp "${BACKUP_DIR}/sshd_config" /etc/ssh/sshd_config
|
|
systemctl restart sshd 2>/dev/null
|
|
return 1
|
|
}
|
|
}
|
|
|
|
check_requirements() {
|
|
[[ $EUID -ne 0 ]] && { print_error "Ce script doit être exécuté en tant que root"; exit 1; }
|
|
[[ ! -f /etc/debian_version ]] && print_warning "Système non Debian/Ubuntu - compatibilité limitée"
|
|
mkdir -p "$BACKUP_DIR"
|
|
touch "$LOG_FILE" "$STATUS_FILE" 2>/dev/null || { print_error "Impossible de créer les fichiers de log"; exit 1; }
|
|
print_success "Prérequis vérifiés"
|
|
}
|
|
|
|
parse_arguments() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--force-all) FORCE_ALL=true; shift ;;
|
|
--force-step=*) FORCE_STEPS+=("${1#*=}"); shift ;;
|
|
--skip-step=*) SKIP_STEPS+=("${1#*=}"); shift ;;
|
|
--cleanup-ssh) AUTO_CLEANUP_SSH="yes"; shift ;;
|
|
--unattended) UNATTENDED=true; AUTO_YES="yes"; shift ;;
|
|
--ssh-port=*) AUTO_SSH_PORT="${1#*=}"; shift ;;
|
|
--timezone=*) AUTO_TIMEZONE="${1#*=}"; shift ;;
|
|
--change-root-pwd) AUTO_CHANGE_ROOT_PWD="yes"; shift ;;
|
|
--yes|-y) AUTO_YES="yes"; shift ;;
|
|
--skip-lynis) AUTO_SKIP_LYNIS="yes"; shift ;;
|
|
*) print_error "Option inconnue: $1"; exit 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
main() {
|
|
parse_arguments "$@"
|
|
check_requirements
|
|
|
|
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 "==================================================" "START"
|
|
|
|
install_security_tools || print_warning "install_security_tools: erreur ignorée"
|
|
change_root_password || print_warning "change_root_password: erreur ignorée"
|
|
detect_open_ports || print_warning "detect_open_ports: erreur ignorée"
|
|
configure_process_accounting || print_warning "configure_process_accounting: erreur ignorée"
|
|
configure_sysctl_security || print_warning "configure_sysctl_security: erreur ignorée"
|
|
configure_log_permissions || print_warning "configure_log_permissions: erreur ignorée"
|
|
configure_pam_password_policy || print_warning "configure_pam_password_policy: erreur ignorée"
|
|
configure_login_defs || print_warning "configure_login_defs: erreur ignorée"
|
|
configure_umask || print_warning "configure_umask: erreur ignorée"
|
|
configure_aide_sha512 || print_warning "configure_aide_sha512: erreur ignorée"
|
|
initialize_aide_db || print_warning "initialize_aide_db: erreur ignorée"
|
|
[[ "$AUTO_ENABLE_CLAMAV" == "yes" ]] && { configure_clamav || print_warning "configure_clamav: erreur ignorée"; }
|
|
configure_chrony || print_warning "configure_chrony: erreur ignorée"
|
|
harden_ssh || print_warning "harden_ssh: erreur ignorée"
|
|
configure_banners || print_warning "configure_banners: erreur ignorée"
|
|
configure_firewall_ports || print_warning "configure_firewall_ports: erreur ignorée"
|
|
configure_fail2ban || print_warning "configure_fail2ban: erreur ignorée"
|
|
remove_unneeded_packages || print_warning "remove_unneeded_packages: erreur ignorée"
|
|
restrict_file_permissions || print_warning "restrict_file_permissions: erreur ignorée"
|
|
disable_risky_kernel_modules || print_warning "disable_risky_kernel_modules: erreur ignorée"
|
|
configure_security_limits || print_warning "configure_security_limits: erreur ignorée"
|
|
verify_packages_integrity || print_warning "verify_packages_integrity: erreur ignorée"
|
|
configure_automatic_updates || print_warning "configure_automatic_updates: erreur ignorée"
|
|
configure_aide_cron || print_warning "configure_aide_cron: erreur ignorée"
|
|
harden_smtp_banner || print_warning "harden_smtp_banner: erreur ignorée"
|
|
harden_systemd_services || print_warning "harden_systemd_services: erreur ignorée"
|
|
configure_advanced_pam || print_warning "configure_advanced_pam: erreur ignorée"
|
|
check_partition_layout || print_warning "check_partition_layout: erreur ignorée"
|
|
check_vmlinuz || print_warning "check_vmlinuz: erreur ignorée"
|
|
run_chkrootkit || print_warning "run_chkrootkit: erreur ignorée"
|
|
prepare_ssh_cleanup || print_warning "prepare_ssh_cleanup: erreur ignorée"
|
|
run_lynis_audit || print_warning "run_lynis_audit: erreur ignorée"
|
|
|
|
[[ "$AUTO_CLEANUP_SSH" == "yes" ]] && { cleanup_ssh_port || print_warning "cleanup_ssh_port: erreur ignorée"; }
|
|
|
|
log_message "Durcissement terminé" "END"
|
|
log_message "==================================================" "END"
|
|
}
|
|
|
|
trap 'print_error "Script interrompu"; exit 130' INT TERM
|
|
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"
|