#!/usr/bin/env bash # ============================================================================= # 00 - Durcissement commun aux deux noeuds (app-01 et db-01) # ============================================================================= # A executer EN PREMIER sur chaque serveur, en sudo : # scp infra/prod/scripts/00-bootstrap-common.sh deploy@:/tmp/ # ssh deploy@ 'sudo bash /tmp/00-bootstrap-common.sh ' # # = app | data # # Idempotent : peut etre relance sans risque. set -euo pipefail ROLE="${1:-}" if [[ "$ROLE" != "app" && "$ROLE" != "data" ]]; then echo "Usage: $0 " >&2 exit 1 fi if [[ "$EUID" -ne 0 ]]; then echo "Ce script doit tourner en root (sudo)." >&2 exit 1 fi log() { printf '\n>>> %s\n' "$*"; } # --- 1. Paquets de base ------------------------------------------------------ log "Mise a jour du systeme" export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get upgrade -y -qq apt-get install -y -qq \ ufw fail2ban unattended-upgrades apt-listchanges \ chrony auditd audispd-plugins \ curl gnupg ca-certificates jq git rsync htop ncdu \ needrestart # --- 2. Mises a jour de securite automatiques -------------------------------- log "Activation des mises a jour de securite automatiques" cat > /etc/apt/apt.conf.d/20auto-upgrades <<'CONF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::AutocleanInterval "7"; CONF cat > /etc/apt/apt.conf.d/50unattended-upgrades <<'CONF' Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; "${distro_id}ESMApps:${distro_codename}-apps-security"; "${distro_id}ESM:${distro_codename}-infra-security"; }; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; // Redemarrage automatique la nuit SI un paquet noyau l'exige. // Fenetre choisie hors des heures ouvrees des transitaires europeens. Unattended-Upgrade::Automatic-Reboot "true"; Unattended-Upgrade::Automatic-Reboot-WithUsers "false"; Unattended-Upgrade::Automatic-Reboot-Time "04:30"; Unattended-Upgrade::Mail ""; CONF systemctl enable --now unattended-upgrades # --- 3. SSH ------------------------------------------------------------------ log "Durcissement SSH" cat > /etc/ssh/sshd_config.d/99-xpeditis-hardening.conf <<'CONF' # Authentification par cle uniquement. PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no ChallengeResponseAuthentication no PermitEmptyPasswords no PubkeyAuthentication yes AuthenticationMethods publickey # Reduction de surface. X11Forwarding no AllowAgentForwarding no PermitTunnel no GatewayPorts no # Anti brute-force / sessions fantomes. MaxAuthTries 3 MaxSessions 5 LoginGraceTime 20 ClientAliveInterval 300 ClientAliveCountMax 2 # Cryptographie moderne uniquement. KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com Macs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com AllowUsers deploy CONF # Retire les cles d'hote faibles si presentes. rm -f /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key* || true sshd -t systemctl restart ssh 2>/dev/null || systemctl restart sshd # --- 4. fail2ban ------------------------------------------------------------- log "Configuration fail2ban" cat > /etc/fail2ban/jail.d/xpeditis.local <<'CONF' [DEFAULT] bantime = 1h findtime = 10m maxretry = 4 backend = systemd # Ne jamais se bannir soi-meme depuis le reseau prive. ignoreip = 127.0.0.1/8 ::1 10.10.0.0/16 [sshd] enabled = true mode = aggressive maxretry = 3 bantime = 24h CONF systemctl enable --now fail2ban systemctl restart fail2ban # --- 5. Parametres noyau ----------------------------------------------------- log "Durcissement sysctl" cat > /etc/sysctl.d/99-xpeditis-hardening.conf <<'CONF' # Reseau net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.all.log_martians = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.tcp_syncookies = 1 net.ipv6.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_source_route = 0 # Memoire / noyau kernel.randomize_va_space = 2 kernel.kptr_restrict = 2 kernel.dmesg_restrict = 1 kernel.yama.ptrace_scope = 1 fs.protected_hardlinks = 1 fs.protected_symlinks = 1 fs.suid_dumpable = 0 # Capacite : k3s et PostgreSQL ouvrent beaucoup de descripteurs. fs.file-max = 2097152 fs.inotify.max_user_instances = 8192 fs.inotify.max_user_watches = 524288 CONF sysctl --system >/dev/null # --- 6. Journalisation ------------------------------------------------------- log "Limitation des journaux systemd (evite de saturer le disque)" mkdir -p /etc/systemd/journald.conf.d cat > /etc/systemd/journald.conf.d/99-xpeditis.conf <<'CONF' [Journal] SystemMaxUse=2G SystemMaxFileSize=200M MaxRetentionSec=30day Compress=yes CONF systemctl restart systemd-journald # --- 7. Horloge -------------------------------------------------------------- log "Synchronisation horaire (obligatoire : JWT, TLS, audit_logs)" timedatectl set-timezone Europe/Paris systemctl enable --now chrony # --- 8. Audit ---------------------------------------------------------------- log "Regles auditd minimales" cat > /etc/audit/rules.d/99-xpeditis.rules <<'CONF' -w /etc/passwd -p wa -k identity -w /etc/shadow -p wa -k identity -w /etc/ssh/sshd_config -p wa -k sshd -w /etc/ssh/sshd_config.d/ -p wa -k sshd -w /etc/sudoers -p wa -k sudoers -w /etc/sudoers.d/ -p wa -k sudoers -w /var/log/auth.log -p wa -k authlog -a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k rootcmd CONF augenrules --load >/dev/null 2>&1 || true systemctl enable --now auditd # --- 9. Pare-feu local ------------------------------------------------------- # Le firewall Hetzner Cloud ne filtre QUE les interfaces publiques. UFW prend # en charge le reseau prive, ou transite le trafic PostgreSQL/Redis. log "Configuration UFW (role: $ROLE)" ufw --force reset >/dev/null ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp comment 'SSH' if [[ "$ROLE" == "app" ]]; then ufw allow 80/tcp comment 'HTTP (redirection + ACME)' ufw allow 443/tcp comment 'HTTPS' ufw allow 6443/tcp comment 'API k3s' # Reseau prive : le noeud app doit joindre db-01, pas l'inverse. ufw allow from 10.10.0.0/16 to any port 10250 proto tcp comment 'kubelet metrics' else # Seul app-01 (IP privee) peut atteindre PostgreSQL et Redis. APP_PRIVATE_IP="${APP_PRIVATE_IP:-10.10.1.10}" ufw allow from "${APP_PRIVATE_IP}" to any port 5432 proto tcp comment 'PostgreSQL <- app-01' ufw allow from "${APP_PRIVATE_IP}" to any port 6379 proto tcp comment 'Redis <- app-01' fi ufw --force enable ufw status verbose # --- 10. Verifications finales ---------------------------------------------- log "Verifications" echo " SSH root login : $(sshd -T 2>/dev/null | grep -i '^permitrootlogin' || echo '?')" echo " Password auth : $(sshd -T 2>/dev/null | grep -i '^passwordauthentication' || echo '?')" echo " fail2ban : $(systemctl is-active fail2ban)" echo " unattended-upgr. : $(systemctl is-active unattended-upgrades)" echo " auditd : $(systemctl is-active auditd)" echo " chrony : $(systemctl is-active chrony)" log "Durcissement commun termine pour le role '$ROLE'." echo "Etape suivante :" if [[ "$ROLE" == "app" ]]; then echo " sudo bash 02-setup-k3s-server.sh" else echo " sudo bash 01-setup-data-node.sh" fi