check_deb_status() { local type="$1" params="$2" case "$type" in "pkg") dpkg -s "$params" >/dev/null 2>&1 ;; "sys") func="check_deb_${type}_${params}" if declare -F "$func" >/dev/null; then "$func" else return 1 fi esac } update_cron_marker(){ # ------------------------------------------------------------------ # Installation du cron # ------------------------------------------------------------------ local marker="# $1" ( crontab -l 2>/dev/null | sed "/^${marker//\//\\/}$/,/^${marker//\//\\/}$/d" echo "$marker" echo "$2" echo "$marker" ) | crontab - msg_success "Tâche cron installée avec succès." } do_deb_install_action() { local type="$1" params="$2" func case "$type" in "pkg") $SUDO apt-get install -y "$params" ;; "sys") func="do_deb_${type}_${params}" if declare -F "$func" >/dev/null; then "$func" else msg_error "la fonction do_deb_${type}_${params} n'est pas définie...(skip)" fi esac } check_deb_sys_ipv6(){ if [[ $(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null) -eq 1 ]]; then return 0 else return 1 fi } check_deb_sys_locale_install() { # On cherche spécifiquement les lignes dé commentées pour fr_FR ET en_US if grep -q "^fr_FR.UTF-8" /etc/locale.gen && grep -q "^en_US.UTF-8" /etc/locale.gen; then return 0 else return 1 fi } check_deb_sys_zabbix(){ if dpkg -s zabbix-agent2 >/dev/null 2>&1; then return 0 else return 1 fi } check_deb_sys_zabbix_conf(){ if dpkg -s zabbix-agent2 >/dev/null 2>&1; then return 1 else return 0 fi } check_deb_sys_msmtp(){ return 1 } check_deb_sys_ntp_setup() { return 1 } check_deb_sys_mail(){ if grep -q "ADMIN_MAIL=" /etc/environment 2>/dev/null; then return 0 else return 1 fi } check_deb_sys_fail2ban() { # Si le paquet n'est PAS installé, on ne propose pas la config if ! dpkg -s fail2ban >/dev/null 2>&1; then return 0 # Masquer (on attend l'étape 1) fi # Si le paquet est là, on affiche la config tant que jail.local n'existe pas if [[ ! -f /etc/fail2ban/jail.local ]]; then return 1 # AFFICHER else return 0 # MASQUER (Déjà configuré) fi } check_deb_sys_apparmor(){ if is_sys_lxc; then ! systemctl is-enabled apparmor >/dev/null 2>&1 #Container else [[ $($SUDO cat /sys/module/apparmor/parameters/enabled 2>/dev/null) == "Y" ]] #VM fi } check_deb_sys_ufw() { if ! dpkg -s ufw >/dev/null 2>&1; then return 0 else return 1 fi } check_deb_sys_gen_ssh_key(){ if [[ -f "$RSA_KEY_FILE" ]]; then return 0 fi return 1 } check_deb_sys_deploy_ssh_key(){ if [[ -f "$RSA_KEY_FILE" ]]; then return 1 fi return 0 } check_deb_sys_motd(){ if find /etc/update-motd.d/ -type f -perm /111 | grep -q .; then return 1 else return 0 fi } check_deb_sys_clone_ssh_key(){ if [[ -f "$RSA_KEY_FILE" ]]; then return 1 fi return 0 } check_deb_sys_cert_install(){ local marker="# $CONFIG_DEB_INSTALL_DEFAULT_CA_CRON" if crontab -l 2>/dev/null | grep -q "$marker"; then return 0 else return 1 fi } check_deb_sys_script_update(){ local marker="# $CONFIG_DEB_INSTALL_CRON_UPDATE" if crontab -l 2>/dev/null | grep -q "$marker"; then return 0 else return 1 fi } do_deb_sys_script_update(){ local full_command script_path WELCOME_SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd)" script_path="${WELCOME_SCRIPT_PATH}/script/update.sh" chmod +x "$script_path" full_command="${WELCOME_SCRIPT_PATH}/script/update.sh >> /var/log/script-update.log 2>&1" ## All days at 3AM update_cron_marker "$CONFIG_DEB_INSTALL_CRON_UPDATE" "0 3 * * * $full_command" } do_deb_sys_locale_install(){ msg_info "installation du package si besoin" apt install locales msg_info "activation des locales Fr et En" sed -i '/fr_FR.UTF-8/s/^# //' /etc/locale.gen sed -i '/en_US.UTF-8/s/^# //' /etc/locale.gen msg_info "génération des locales Fr et En" locale-gen en_US.UTF-8 locale-gen fr_FR.UTF-8 msg_success "opération terminée" } do_deb_sys_motd(){ $SUDO chmod -x /etc/update-motd.d/* msg_success "Les fichiers dans /etc/update-motd.d/ ne sont plus executable" msg_success "Ils ne seront donc plus lancé à la connexion" } do_deb_sys_gen_ssh_key() { if [[ -f "$RSA_KEY_FILE" ]]; then msg_success "Une clé SSH existe déjà : $RSA_KEY_FILE" # On affiche l'empreinte pour info ssh-keygen -l -f "$RSA_KEY_FILE" else msg_info "Génération d'une nouvelle clé RSA (4096 bits)..." mkdir -p "$HOME/.ssh" chmod 700 "$HOME/.ssh" # -t rsa : type RSA # -b 4096 : taille robuste # -N "" : pas de mot de passe (passphrase vide pour l'auto) # -f : chemin du fichier ssh-keygen -t rsa -b 4096 -N "" -f "$RSA_KEY_FILE" msg_success "Clé générée avec succès." fi } do_deb_sys_deploy_ssh_key() { local remote_user remote_host remote_port msg_info "Déploiement de la clé sur un serveur distant" # 1. Vérification locale if [[ ! -f "$HOME/.ssh/id_rsa.pub" ]]; then msg_error "Erreur : Aucune clé publique trouvée. Générez-en une d'abord." return fi # 2. Saisie des infos distantes read -rp "Utilisateur distant (ex: root) : " remote_user read -rp "IP ou Hostname distant : " remote_host read -rp "Port SSH (par défaut 22) : " remote_port remote_port=${remote_port:-22} msg_info "Tentative de déploiement sur ${remote_user}@${remote_host}..." # 3. Déploiement intelligent # ssh-copy-id vérifie tout seul si la clé existe déjà sur le serveur ! if ssh-copy-id -p "$remote_port" "${remote_user}@${remote_host}"; then msg_success "La clé a été installée (ou était déjà présente)." msg_info "Test de connexion : ssh -p $remote_port ${remote_user}@${remote_host}" else msg_error "Le déploiement a échoué. Vérifiez vos accès ou le mot de passe." fi } do_deb_sys_clone_ssh_key() { local remote_user remote_host remote_port local ssh_dir="$HOME/.ssh" local key_file="$ssh_dir/id_rsa" msg_info "Clonage de l'identité SSH vers un hôte distant" # 1. Vérification locale if [[ ! -f "$key_file" ]]; then msg_error "Erreur : Aucune clé locale ($key_file) à copier." return fi # 2. Saisie des infos read -rp "Utilisateur distant : " remote_user read -rp "IP/Hostname distant : " remote_host read -rp "Port SSH (22) : " remote_port remote_port=${remote_port:-22} # 3. Confirmation (car c'est sensible) msg_warning "Ceci va copier votre clé PRIVÉE sur $remote_host." read -rp "Êtes-vous sûr ? (o/N) : " confirm [[ "$confirm" != "o" && "$confirm" != "O" ]] && return # 4. Envoi et configuration des droits en une seule commande # On utilise tar pour conserver les droits et créer le dossier si besoin msg_info "Transfert en cours..." tar -C "$ssh_dir" -cf - id_rsa id_rsa.pub | ssh -p "$remote_port" "${remote_user}@${remote_host}" " mkdir -p ~/.ssh && \ tar -C ~/.ssh -xf - && \ chmod 700 ~/.ssh && \ chmod 600 ~/.ssh/id_rsa && \ chmod 644 ~/.ssh/id_rsa.pub && \ echo 'Clés copiées et permissions réglées.' " if [[ $? -eq 0 ]]; then msg_success "Identité clonée avec succès sur ${remote_host}." else msg_error "Le transfert a échoué." fi } do_deb_sys_ntp() { msg_info "Configuration de l'heure (Paris) et du NTP..." # 1. On règle sur Paris au lieu de UTC $SUDO timedatectl set-timezone Europe/Paris msg_info "Fuseau horaire réglé sur Europe/Paris." # 2. Configuration NTP (On ajoute des serveurs français pour la précision) $SUDO tee /etc/systemd/timesyncd.conf </dev/null [Time] NTP=0.fr.pool.ntp.org 1.fr.pool.ntp.org pool.ntp.org FallbackNTP=8.8.8.8 1.1.1.1 EOF $SUDO timedatectl set-ntp true $SUDO systemctl restart systemd-timesyncd msg_success "Heure locale réglée : $(date)" } do_deb_sys_ufw() { local ports_to_allow=() input_port="" current_ssh_port current_rules msg_info "Configuration interactive du Firewall UFW" # 1. On montre l'existant SANS y toucher current_rules=$($SUDO ufw status | grep -v "(v6)" | grep "ALLOW" | awk '{print $1}' | cut -d'/' -f1 | tr '\n' ' ') if [[ -n "$current_rules" ]]; then msg_warning "Ports actuellement ouverts : ${COLOR_WHITE}${current_rules}${NONE}" msg_warning "Toutes les autres règles existantes seront supprimées" msg_warning "Veuillez saisir tous les ports à ouvrir" msg_warning "Même si il étaient déjà ouvert avant.\n" fi msg_info "Liste des ports tcp sortant ouvert" $SUDO ss -tlnwp | grep -v -E '(127\.0\.0\.1|::1)' | awk ' BEGIN {print "State", "Local_Address:Port", "Process"} NR==1 {next} { split($4, a, ":"); port=a[length(a)] } !seen[port]++ {print $1, $2, $5}' | column -t # 2. Boucle de saisie (on remplit uniquement le tableau en mémoire) while true; do read -rp "Entrez un port à ouvrir (ou 'f' pour terminer) : " input_port [[ "$input_port" == "f" ]] && break if [[ ! "$input_port" =~ ^[0-9]+$ ]]; then msg_error "Port invalide." continue fi if [[ " ${ports_to_allow[@]} " =~ " ${input_port} " ]]; then msg_warning "Déjà dans la liste." else ports_to_allow+=("$input_port") msg_success "Port $input_port ajouté à la file d'attente." fi done # 3. Vérification de sécurité SSH (Toujours en mémoire) current_ssh_port=$(ss -tlnp | grep sshd | awk '{print $4}' | awk -F':' '{print $NF}' | head -n1) current_ssh_port=${current_ssh_port:-22} if [[ ! " ${ports_to_allow[@]} " =~ " ${current_ssh_port} " ]]; then msg_warning "ATTENTION : Votre port SSH ($current_ssh_port) n'est pas inclus !" read -rp "L'ajouter ? (O/n) : " add_ssh [[ "$add_ssh" != "n" ]] && ports_to_allow+=("$current_ssh_port") fi # 4. LE MOMENT CRITIQUE : Confirmation avant application echo -e "\n${COLOR_YELLOW}RÉCAPITULATIF :${NONE}" echo -e "Les ports suivants vont être ouverts : ${COLOR_LIGHT_BLUE}${ports_to_allow[*]}${NONE}" msg_error "Toutes les autres règles existantes seront supprimées." read -rp "Appliquer ces changements maintenant ? (o/N) : " confirm if [[ "$confirm" != "o" && "$confirm" != "O" ]]; then msg_info "Action annulée. Le firewall n'a pas été modifié." return fi # 5. APPLICATION RÉELLE (Seulement si confirmé) msg_info "Application des modifications..." # On reset seulement maintenant $SUDO ufw --force reset >/dev/null $SUDO ufw default deny incoming $SUDO ufw default allow outgoing for port in "${ports_to_allow[@]}"; do $SUDO ufw allow "$port"/tcp >/dev/null done $SUDO sed -i 's/IPV6=yes/IPV6=no/' /etc/default/ufw if $SUDO ufw --force enable; then msg_success "Firewall mis à jour et activé." fi } do_deb_sys_fail2ban() { msg_info "Configuration de Fail2Ban..." # On crée la configuration personnalisée $SUDO tee /etc/fail2ban/jail.local </dev/null) != "Y" ]]; then if [ -f /etc/default/grub ]; then $SUDO sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="apparmor=1 security=apparmor /' /etc/default/grub $SUDO update-grub msg_warning "Activation GRUB faite. Reboot nécessaire." fi else msg_success "AppArmor est déjà actif dans le Kernel." fi fi } do_deb_sys_zabbix(){ local ip # 1. Désinstallation de l'ancien agent s'il existe if dpkg -s zabbix-agent >/dev/null 2>&1; then msg_info "Ancien Zabbix Agent détecté, suppression..." $SUDO systemctl stop zabbix-agent >/dev/null 2>&1 $SUDO apt-get purge -y zabbix-agent $SUDO apt-get autoremove -y fi read -rp "IP Zabbix [${CONFIG_DEB_INSTALL_ZABBIX_SERVER_IP}]: " ip; ip=${ip:-${CONFIG_DEB_INSTALL_ZABBIX_SERVER_IP}} $SUDO apt-get install -y zabbix-agent2; $SUDO sed -i "s/^Server=.*/Server=$ip/" /etc/zabbix/zabbix_agent2.conf $SUDO sed -i "s/^ServerActive=.*/ServerActive=$ip/" /etc/zabbix/zabbix_agent2.conf # OPTIONNEL : Force l'agent à envoyer son propre nom d'hôte système $SUDO sed -i "s/^# HostnameItem=.*/HostnameItem=system.hostname/" /etc/zabbix/zabbix_agent2.conf $SUDO sed -i "s/^Hostname=.*/# Hostname=Zabbix server/" /etc/zabbix/zabbix_agent2.conf $SUDO systemctl enable zabbix-agent2 $SUDO systemctl restart zabbix-agent2 msg_success "Zabbix agent 2 configuré." } do_deb_sys_zabbix_conf(){ local ip read -rp "IP Zabbix [${CONFIG_DEB_INSTALL_ZABBIX_SERVER_IP}]: " ip; ip=${ip:-${CONFIG_DEB_INSTALL_ZABBIX_SERVER_IP}} $SUDO sed -i "s/^Server=.*/Server=$ip/" /etc/zabbix/zabbix_agent2.conf $SUDO sed -i "s/^ServerActive=.*/ServerActive=$ip/" /etc/zabbix/zabbix_agent2.conf # OPTIONNEL : Force l'agent à envoyer son propre nom d'hôte système $SUDO sed -i "s/^# HostnameItem=.*/HostnameItem=system.hostname/" /etc/zabbix/zabbix_agent2.conf $SUDO sed -i "s/^Hostname=.*/# Hostname=Zabbix server/" /etc/zabbix/zabbix_agent2.conf $SUDO systemctl enable zabbix-agent2 $SUDO systemctl restart zabbix-agent2 msg_success "Zabbix agent 2 configuré." } do_deb_sys_ipv6(){ # Application des paramètres sysctl # On cible 'all', 'default' et 'lo' (loopback) $SUDO sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null $SUDO sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null $SUDO sysctl -w net.ipv6.conf.lo.disable_ipv6=1 >/dev/null # Rendre les changements persistants après redémarrage $SUDO cat < /etc/sysctl.d/99-disable-ipv6.conf net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1 EOF msg_success "IPv6 désactivé avec succès" } do_deb_sys_mail(){ local m read -rp "Mail admin [${CONFIG_DEB_INSTALL_SERVER_ADMIN_MAIL}]: " m; m=${m:-$CONFIG_DEB_INSTALL_SERVER_ADMIN_MAIL} echo "export ADMIN_MAIL=\"$m\"" | $SUDO tee -a /etc/environment > /dev/null # shellcheck disable=SC2034 export ADMIN_MAIL=$m # Reload env source /etc/environment msg_success "ADMIN_MAIL configuré avec $m" } do_deb_sys_static_ip() { local interface interface=$(ip -o link show | awk -F': ' '{print $2}' | grep -v 'lo' | head -n1) echo -e "${COLOR_WHITE}Configuration de l'interface : ${COLOR_CYAN}$interface${NONE}" read -rp "Entrez l'IP (ex: 192.168.1.50) : " new_ip read -rp "Entrez la Passerelle (Gateway) : " new_gw read -rp "Entrez le DNS Primaire (ex: 1.1.1.1) : " dns1 read -rp "Entrez le DNS Secondaire (ex: 8.8.8.8) : " dns2 # Validation minimale if [[ -z "$new_ip" || -z "$new_gw" || -z "$dns1" ]]; then msg_error "IP, Gateway ou DNS Primaire manquant. Annulation." return 1 fi # Si le DNS2 est vide, on peut mettre une valeur par défaut ou laisser vide [[ -z "$dns2" ]] && dns2="8.8.4.4" # --- CAS 1 : NETPLAN (Ubuntu / Debian récentes) --- if [ -d "/etc/netplan" ]; then msg_info "Configuration via Netplan..." $SUDO bash -c "cat < /etc/netplan/99-frogg-static.yaml network: version: 2 renderer: networkd ethernets: $interface: addresses: [$new_ip/24] routes: [{to: default, via: $new_gw}] nameservers: addresses: [$dns1, $dns2] EOF" $SUDO netplan apply # --- CAS 2 : INTERFACES (Debian classique) --- elif [ -f "/etc/network/interfaces" ]; then msg_info "Configuration via /etc/network/interfaces..." $SUDO bash -c "cat < /etc/network/interfaces.d/$interface auto $interface iface $interface inet static address $new_ip netmask 255.255.255.0 gateway $new_gw dns-nameservers $dns1 $dns2 EOF" $SUDO systemctl restart networking else msg_error "Aucun gestionnaire réseau trouvé." return 1 fi msg_success "$interface > IP [$new_ip] Gateway[$new_gw] DNS[$dns1, $dns2]" } do_deb_sys_hostname() { local old_h current_ip old_h=$(hostname) echo -e "${COLOR_WHITE}Nom actuel : ${COLOR_CYAN}$old_h${NONE}" read -rp "Nouveau Hostname (FQDN recommandé, ex: machine.domaine.com) : " new_h if [[ -n "$new_h" ]]; then # 1. On définit le hostname dans le système $SUDO hostnamectl set-hostname "$new_h" # 2. On extrait le nom court local short_h="${new_h%%.*}" # 3. Mise à jour de /etc/hosts # STRATÉGIE : On identifie la ligne qui contient l'ancien hostname # et on la remplace intégralement par "IP FQDN NOM_COURT" # On récupère l'IP associée à l'ancien hostname dans /etc/hosts current_ip=$(grep -w "$old_h" /etc/hosts | awk '{print $1}' | head -n1) if [[ -n "$current_ip" ]]; then msg_info "Mise à jour de la ligne pour l'IP $current_ip..." # On remplace toute la ligne qui commence par cette IP $SUDO sed -i "s/^$current_ip.*/$current_ip\t$new_h $short_h/" /etc/hosts else # Si on ne trouve pas l'IP, on ajoute une ligne propre sur la loopback locale msg_warning "IP non trouvée dans /etc/hosts, ajout sur 127.0.1.1" echo -e "127.0.1.1\t$new_h $short_h" | $SUDO tee -a /etc/hosts > /dev/null fi msg_success "Hostname configuré : $new_h ($short_h)" fi } do_deb_sys_msmtp() { local m_host m_port m_user m_from m_pass default_from msg_info "Installation et configuration de MSMTP..." # Installation du paquet $SUDO apt-get update -qq && $SUDO apt-get install -y msmtp msmtp-mta ca-certificates -qq default_from="srv-$(hostname -s)${CONFIG_DEB_INSTALL_SERVER_SMTP_FROM}" # Saisie des informations read -rp "Serveur SMTP (host) [${CONFIG_DEB_INSTALL_SERVER_SMTP_HOST}]: " m_host read -rp "Port (ex: 587 ou 465) [${CONFIG_DEB_INSTALL_SERVER_SMTP_PORT}]: " m_port read -rp "Email expéditeur (from) [$default_from]: " m_from read -rp "Utilisateur (user/login) [${CONFIG_DEB_INSTALL_SERVER_SMTP_LOGIN}]: " m_user read -s -rp "Mot de passe : " m_pass m_host=${m_host:-$CONFIG_DEB_INSTALL_SERVER_SMTP_HOST} m_port=${m_port:-$CONFIG_DEB_INSTALL_SERVER_SMTP_PORT} m_from=${m_from:-$default_from} m_user=${m_user:-$CONFIG_DEB_INSTALL_SERVER_SMTP_LOGIN} echo "" # Pour revenir à la ligne après le mot de passe masqué # Construction du fichier msmtprc $SUDO bash -c "cat < /etc/msmtprc # Configuration générée par Frogg Deploy defaults auth on tls on tls_trust_file /etc/ssl/certs/ca-certificates.crt syslog LOG_MAIL #logfile /var/log/msmtp.log account default host $m_host port $m_port from $m_from user $m_user password $m_pass EOF" # Sécurisation du fichier (Indispensable car contient un mot de passe) $SUDO chmod 600 /etc/msmtprc $SUDO chown root:root /etc/msmtprc msg_success "MSMTP configuré. Test d'envoi recommandé : echo 'Test' | mail -s 'Test sujet' ton@mail.com" } do_deb_sys_cert_install_ca_server(){ local full_command="${WELCOME_SCRIPT_PATH}/script/ca_server_renew.sh" msg_info "Ajout des droits d execution du script" chmod +x "$full_command" msg_info "Renouvellement du certificat" eval "$full_command" msg_info "Ajout du renouvellement automatique du certificat" update_cron_marker "$CONFIG_DEB_INSTALL_DEFAULT_CA_CRON" "0 0 1 * * $full_command >> /var/log/cert-renew.log 2>&1" } do_deb_sys_cert_install(){ # cas du server CA if [ "$(hostname)" = "$CONFIG_DEB_INSTALL_DEFAULT_CA_SERVER" ] || [ "$(hostname -f)" = "$CONFIG_DEB_INSTALL_DEFAULT_CA_SERVER" ]; then msg_warning "server CA détecté, mise en place d'un renew spécifique..." do_deb_sys_cert_install_ca_server return 0 fi local ca_ip wildcard_domain ca_url ca_fingerprint base_domain marker root_crt input_ip step_path \ system_target inter_target cert_group load_state unit svc_user current_group \ cert_dir cert_crt cert_key cert_key cert_pfx pfx_cmd pfx_input step_bin renew_cmd restart_cmd="" \ unit full_command cert_fullchain proxmox_cmd # Fichiers de certificats cert_dir="${CONFIG_DEB_INSTALL_DEFAULT_CA_INSTALL_PATH}" cert_crt="${cert_dir}/${CONFIG_DEB_INSTALL_DEFAULT_CA_FILE_CRT}" cert_key="${cert_dir}/${CONFIG_DEB_INSTALL_DEFAULT_CA_FILE_KEY}" cert_pfx="${cert_dir}/${CONFIG_DEB_INSTALL_DEFAULT_CA_FILE_PFX}" # Groupe utilisé pour l'accès aux certificats cert_group="ssl-cert" msg_info "=== Configuration Automatisée du Client PKI ===" # 1. Saisie des informations de base read -rp "IP du serveur CA [${CONFIG_DEB_INSTALL_DEFAULT_CA_SERVER}] : " input_ip ca_ip=${input_ip:-${CONFIG_DEB_INSTALL_DEFAULT_CA_SERVER}} read -rp "Domaine Wildcard [${CONFIG_DEB_INSTALL_DEFAULT_CA_WILDCARD}] : " INPUT_DOMAIN wildcard_domain=${INPUT_DOMAIN:-${CONFIG_DEB_INSTALL_DEFAULT_CA_WILDCARD}} msg_info "Configuration retenue : IP=$ca_ip | Domaine=$wildcard_domain" ca_url="https://$ca_ip" # ================================================================== # [1/7] Récupération de l'empreinte via HTTPS # ================================================================== # 2. Récupération automatique de la Fingerprint via HTTPS msg_warning "[1/7] Récupération de l'empreinte via HTTPS..." # Cette commande récupère le certificat du serveur et calcule son empreinte SHA256 ca_fingerprint=$(openssl s_client -connect "${ca_ip}:443" /dev/null | openssl x509 -fingerprint -sha256 -noout | cut -d'=' -f2 | tr -d ':') if [ -z "$ca_fingerprint" ]; then msg_error "Erreur : Impossible de contacter le serveur sur le port 443. Vérifie l'IP." return 1 fi msg_info "Empreinte détectée : ${GREEN}$ca_fingerprint${NONE}" # ================================================================== # [2/7] Installation du paquet Step CLI # ================================================================== msg_warning "[2/7] Installation du paquet Step CLI..." # Installation de Step CLI (si besoin) if ! command -v step &> /dev/null; then if dpkg -i "${WELCOME_SCRIPT_PATH}/doc/${CONFIG_DEB_INSTALL_DEFAULT_CA_STEP}" > /dev/null 2>&1; then msg_success "Installation du paquet ${CONFIG_DEB_INSTALL_DEFAULT_CA_STEP}" else msg_error "Échec de l'installation du paquet ${CONFIG_DEB_INSTALL_DEFAULT_CA_STEP}" return 1 fi else msg_success "paquet Step CLI déjà installé" fi # ================================================================== # [3/7] Liaison et génération du certificat # ================================================================== # Configuration et Certificat msg_warning "[3/7] Liaison et génération du certificat..." # 1. On force un environnement de travail propre step_path="/tmp/step-config" rm -rf "$step_path" mkdir -p "$step_path" # CRUCIAL : On dit à step d'utiliser ce dossier pour TOUTES ses opérations export STEPPATH="$step_path" # 2. Utilisation de la Fingerprint confirmée #ca_fingerprint="4873b9eaeb8a7643475939b4035221bd1bc3acd0db00e94df5a76d771459f439" root_ca_fingerprint=$(ssh "root@${ca_ip}" "step certificate fingerprint /var/lib/step-ca/.step/certs/root_ca.crt") if [ -z "$root_ca_fingerprint" ]; then msg_error " Impossible de récupérer le fingerprint." return 1 else msg_info "fingerprint récupéré : ${root_ca_fingerprint}" fi # 3. Liaison (Bootstrap) step ca bootstrap --ca-url "$ca_url" --fingerprint "$root_ca_fingerprint" --force # Chemin du certificat racine récupéré root_crt="$step_path/certs/root_ca.crt" # 4. Génération du Wildcard mkdir -p "${CONFIG_DEB_INSTALL_DEFAULT_CA_INSTALL_PATH}" base_domain="${wildcard_domain#*.}" step ca certificate "$wildcard_domain" \ "${cert_crt}" \ "${cert_key}" \ --ca-url "$ca_url" \ --root "$root_crt" \ --san "$wildcard_domain" \ --san "$base_domain" \ --not-after=8760h \ --force msg_success "Certificat généré avec succès dans ${CONFIG_DEB_INSTALL_DEFAULT_CA_INSTALL_PATH}/" # ================================================================== # [4/7] Installation automatique du Root CA # ================================================================== msg_warning "[4/7] Installation automatique du Root CA..." # 1. Définition des chemins # On s'assure de retrouver le fichier même si la variable a sauté system_target="/usr/local/share/ca-certificates/step-ca-frogg.crt" inter_target="/usr/local/share/ca-certificates/step-ca-intermediate-frogg.crt" ### --- SECTION INTERMEDIATE --- ### if [ -f "$cert_crt" ]; then # On extrait le DEUXIÈME certificat du fichier (l'intermédiaire) # On utilise awk pour isoler le second bloc -----BEGIN...END----- awk '/-----BEGIN CERTIFICATE-----/{i++} i==2' "$cert_crt" > "$inter_target" if [ -s "$inter_target" ]; then msg_success "Certificat intermédiaire extrait avec succès." else # Si awk échoue ou que le fichier est simple, on peut tenter un téléchargement direct msg_warn "Extraction échouée, tentative de récupération via step..." step ca root "$inter_target" --ca-url "$ca_url" --fingerprint "$ca_fingerprint" --force > /dev/null 2>&1 fi else msg_warn "${cert_crt} introuvable, impossible d'extraire l'intermédiaire pour le moment." fi ### --- FIN SECTION INTERMEDIATE --- ### if [ -f "$root_crt" ]; then # 2. On nettoie le certificat (format PEM pur) pour éviter l'erreur 'rehash' # Cela extrait uniquement le certificat et ignore le texte inutile openssl x509 -in "$root_crt" -out "$system_target" # 3. Mise à jour du magasin (sans --fresh pour éviter les warnings inutiles) if update-ca-certificates > /dev/null 2>&1; then msg_success "Le système fait maintenant confiance au Root CA Frogg." else msg_error "Échec lors de la mise à jour des certificats système." fi else msg_error "Source introuvable dans $root_crt. Vérifie que l'étape 3 a réussi." fi # ---(Cron & Nettoyage final) --- # C'est seulement MAINTENANT qu'on peut supprimer rm -rf "${step_path}" # ================================================================== # [5/7] ATTRIBUTION DES DROITS # ================================================================== msg_warning "[5/7] Finalisation des droits et redémarrage des services..." # Création du groupe si nécessaire groupadd -f "$cert_group" # Liste des services valides et actifs (utilisée aussi pour le cron) local -a active_services=() local -A processed_users=() # Permissions de base (appliquées une seule fois) chown root:"$cert_group" "$cert_key" "$cert_pfx" 2>/dev/null chmod 640 "$cert_key" "$cert_pfx" 2>/dev/null chmod 644 "$cert_crt" 2>/dev/null # Groupe utilisé pour l'accès aux certificats cert_group="ssl-cert" # Parcours des services configurés pour ajouter l utilisateur au groupe ssl for svc in $CONFIG_DEB_INSTALL_DEFAULT_CA_SERVICES; do unit="${svc%.service}.service" # Vérifie que l'unité existe réellement load_state="$(systemctl show "$unit" --property=LoadState --value 2>/dev/null)" [[ "$load_state" != "loaded" ]] && continue # Vérifie que le service est actif systemctl is-active --quiet "$unit" || continue # Ajout à la liste des services à redémarrer active_services+=("$unit") # Récupération de l'utilisateur défini dans le service (plus fiable que pgrep/ps) svc_user="$(systemctl show "$unit" --property=User --value 2>/dev/null)" # Si User= n'est pas défini, systemd exécute le service en root [[ -z "$svc_user" ]] && svc_user="root" # Ignore root [[ "$svc_user" == "root" ]] && continue # Évite de traiter plusieurs fois le même utilisateur [[ -n "${processed_users[$svc_user]}" ]] && continue processed_users["$svc_user"]=1 msg_info "🔧 Configuration de $unit (Utilisateur : $svc_user)" # Ajout de l'utilisateur au groupe ssl-cert if usermod -aG "$cert_group" "$svc_user"; then msg_success "Utilisateur $svc_user ajouté au groupe $cert_group." else msg_error "Impossible d'ajouter $svc_user au groupe $cert_group." continue fi done # Redémarrage immédiat des services pour prise en compte du nouveau groupe for unit in "${active_services[@]}"; do if systemctl restart "$unit"; then msg_success "Service $unit redémarré avec succès." else msg_error "Impossible de redémarrer $unit." fi done # ================================================================== # [6/7] GÉNÉRATION PFX # ================================================================== msg_warning "[6/7] Génération du .pfx..." # Détection du groupe actuel du dossier (normalement ssl-cert) current_group="$(stat -c '%G' "$cert_dir")" # ------------------------------------------------------------------ # Commande de génération du PFX # IMPORTANT: # - -legacy pour compatibilité maximale avec Jellyfin/.NET # - -passout pass: pour mot de passe vide # - exporte la chaîne complète via -certfile si fullchain.crt existe # ------------------------------------------------------------------ pfx_input="$cert_crt" cert_fullchain="${cert_dir}/fullchain.crt" if [[ -f "$cert_fullchain" ]]; then pfx_input="$cert_fullchain" fi pfx_cmd="openssl pkcs12 -export \ -out \"$cert_pfx\" \ -inkey \"$cert_key\" \ -in \"$pfx_input\" \ -passout pass: \ -keypbe AES-256-CBC -certpbe AES-256-CBC \ && chown root:\"$current_group\" \"$cert_pfx\" \ && chmod 640 \"$cert_pfx\"" if eval "$pfx_cmd"; then # chown root:"$current_group" "$cert_key" "$cert_crt" # chmod 640 "$cert_key" # chmod 644 "$cert_crt" msg_success "Fichier PFX généré avec succès." else msg_error "Échec de la génération du fichier PFX." fi # ================================================================== # [7/7] PLANIFICATION # ================================================================== msg_warning "[7/7] Activation du renouvellement automatique..." step_bin="$(command -v step)" if [[ -z "$step_bin" ]]; then msg_error "Le binaire 'step' est introuvable." return 1 fi # ------------------------------------------------------------------ # Commande de renouvellement # ------------------------------------------------------------------ renew_cmd="$step_bin certificate renew \"$cert_crt\" \"$cert_key\" --force" # ------------------------------------------------------------------ # Construction de la liste des services à redémarrer # ------------------------------------------------------------------ if [[ ${#active_services[@]} -gt 0 ]]; then restart_cmd="systemctl restart" for unit in "${active_services[@]}"; do restart_cmd+=" \"$unit\"" done fi # ------------------------------------------------------------------ # Commande complète du cron # ------------------------------------------------------------------ # ajout du certificat à proxmox proxmox_cmd='(command -v pvenode >/dev/null && pvenode cert set "'$cert_crt'" "'$cert_key'" --force 1 || true)' if eval "$proxmox_cmd"; then msg_info "Vérification proxmox effectuée" fi full_command="$renew_cmd >> /var/log/cert-renew.log 2>&1 \ && $pfx_cmd \ && chown root:\"$current_group\" \"$cert_key\" \"$cert_crt\" \ && chmod 640 \"$cert_key\" \ && chmod 644 \"$cert_crt\" \ && $proxmox_cmd" if [[ -n "$restart_cmd" ]]; then full_command+=" && $restart_cmd" msg_success "Creation de la commande Cron." fi # ------------------------------------------------------------------ # Installation du cron # ------------------------------------------------------------------ update_cron_marker "$CONFIG_DEB_INSTALL_DEFAULT_CA_CRON" "0 0 * * 0 $full_command" # ------------------------------------------------------------------ # Redémarrage final (sécurité) # ------------------------------------------------------------------ for unit in "${active_services[@]}"; do if systemctl restart "$unit"; then msg_success "Service $unit redémarré avec succès." else msg_error "Impossible de redémarrer $unit." fi done msg_success "Infrastructure PKI prête. Groupe : $current_group" msg_success " " msg_success " ################################" msg_success " # Tout est prêt et configuré ! # " msg_success " ###########(${CONFIG_DEB_INSTALL_DEFAULT_CA_INSTALL_PATH}/)#" msg_success " " }