From ec59d5933bc382c781f28c0b3562e17ae218da67 Mon Sep 17 00:00:00 2001 From: Nuxgrid Date: Thu, 30 Jul 2026 19:04:12 +0200 Subject: [PATCH] chore: update and add playbook haproxy --- infra/playbook-app-firewall-lockdown.yml | 48 +++++++++ infra/playbook-haproxy.yml | 127 +++++++++++++++++++++++ inventory/host.ini | 2 +- inventory/host_vars/load-balancer.yml | 6 ++ 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 infra/playbook-app-firewall-lockdown.yml create mode 100644 infra/playbook-haproxy.yml create mode 100644 inventory/host_vars/load-balancer.yml diff --git a/infra/playbook-app-firewall-lockdown.yml b/infra/playbook-app-firewall-lockdown.yml new file mode 100644 index 0000000..086e8cc --- /dev/null +++ b/infra/playbook-app-firewall-lockdown.yml @@ -0,0 +1,48 @@ +--- +# Les ports 80/443 sont publiés par Docker (docker-compose du conteneur waf), donc +# UFW seul ne suffit pas : Docker insère ses propres règles NAT/FORWARD directement +# dans iptables et les contourne. On filtre donc dans la chaîne DOCKER-USER, seul +# point garanti d'être évalué avant les règles de Docker. +# +# Résultat : seul le load-balancer (IP VPN) peut atteindre 80/443 sur ces hôtes, +# tout le reste d'internet est bloqué. SSH et le reste du trafic ne sont pas touchés. + +- name: Restreindre l'accès HTTP/HTTPS des serveurs applicatifs au load-balancer + hosts: prod:replica-prod + become: true + gather_facts: false + + vars: + lb_vpn_ip: "{{ hostvars['load-balancer']['vpn_ip'] }}" + + tasks: + - name: Installer iptables-persistent + ansible.builtin.apt: + name: iptables-persistent + state: present + update_cache: yes + + - name: Bloquer les connexions 80/443 non issues du load-balancer + ansible.builtin.iptables: + chain: DOCKER-USER + protocol: tcp + match: multiport + destination_ports: "80,443" + jump: DROP + action: insert + comment: "app-firewall: bloque 80/443 hors load-balancer" + + - name: Autoriser les connexions 80/443 depuis le load-balancer (VPN) + ansible.builtin.iptables: + chain: DOCKER-USER + protocol: tcp + source: "{{ lb_vpn_ip }}/32" + match: multiport + destination_ports: "80,443" + jump: RETURN + action: insert + comment: "app-firewall: autorise 80/443 depuis le load-balancer" + + - name: Sauvegarder les règles iptables (persistance au reboot) + ansible.builtin.command: netfilter-persistent save + changed_when: false diff --git a/infra/playbook-haproxy.yml b/infra/playbook-haproxy.yml new file mode 100644 index 0000000..59bcc96 --- /dev/null +++ b/infra/playbook-haproxy.yml @@ -0,0 +1,127 @@ +--- +# Le certificat mln-uber.club n'est pas généré ici : il existe déjà sur prod-uber +# (utilisé par le conteneur waf du docker-compose). On le récupère et on le copie +# vers le load-balancer. Relancer ce playbook après chaque renouvellement du cert +# sur prod-uber pour resynchroniser HAProxy. + +- name: Récupérer le certificat mln-uber.club depuis prod-uber + hosts: prod-uber + become: true + gather_facts: false + + vars: + # À CONFIRMER : chemin réel du cert sur prod-uber (aucun playbook ne le dépose + # actuellement à cet endroit — à ajuster si différent). + app_cert_dir: "{{ docker_dir }}/certs" + local_cert_sync_dir: /tmp/mln-uber-cert-sync + + tasks: + - name: Créer le dossier temporaire local de synchronisation + ansible.builtin.file: + path: "{{ local_cert_sync_dir }}" + state: directory + mode: "0700" + delegate_to: localhost + become: false + + - name: Rapatrier fullchain.pem et privkey.pem + ansible.builtin.fetch: + src: "{{ app_cert_dir }}/{{ item }}" + dest: "{{ local_cert_sync_dir }}/{{ item }}" + flat: true + loop: + - fullchain.pem + - privkey.pem + + - name: Restreindre les permissions locales de la clé privée + ansible.builtin.file: + path: "{{ local_cert_sync_dir }}/privkey.pem" + mode: "0600" + delegate_to: localhost + become: false + +- name: Déploiement HAProxy (load-balancer) + hosts: load-balancer + become: true + gather_facts: true + + vars: + haproxy_cert_dir: /etc/haproxy/certs + haproxy_cert_path: "{{ haproxy_cert_dir }}/{{ domain_name }}.pem" + local_cert_sync_dir: /tmp/mln-uber-cert-sync + + tasks: + - name: Installer HAProxy + ansible.builtin.apt: + name: haproxy + state: present + update_cache: yes + + - name: Créer le dossier des certificats HAProxy + ansible.builtin.file: + path: "{{ haproxy_cert_dir }}" + state: directory + owner: root + group: root + mode: "0750" + + - name: Copier fullchain.pem et privkey.pem sur le load-balancer + ansible.builtin.copy: + src: "{{ local_cert_sync_dir }}/{{ item }}" + dest: "{{ haproxy_cert_dir }}/{{ item }}" + owner: root + group: root + mode: "0640" + loop: + - fullchain.pem + - privkey.pem + notify: reload haproxy + + - name: Construire le certificat combiné pour HAProxy (fullchain + privkey) + ansible.builtin.shell: > + cat {{ haproxy_cert_dir }}/fullchain.pem + {{ haproxy_cert_dir }}/privkey.pem + > {{ haproxy_cert_path }} + notify: reload haproxy + + - name: Restreindre les permissions du certificat combiné + ansible.builtin.file: + path: "{{ haproxy_cert_path }}" + owner: root + group: root + mode: "0640" + + - name: Ouvrir le port HTTPS dans UFW + ansible.builtin.ufw: + rule: allow + port: "443" + proto: tcp + + - name: Déployer la configuration HAProxy + ansible.builtin.template: + src: ../templates/haproxy.cfg.j2 + dest: /etc/haproxy/haproxy.cfg + owner: root + group: root + mode: "0644" + validate: haproxy -c -f %s + notify: reload haproxy + + - name: Démarrer et activer HAProxy + ansible.builtin.systemd: + name: haproxy + state: started + enabled: yes + + - name: Nettoyer les fichiers temporaires locaux + ansible.builtin.file: + path: "{{ local_cert_sync_dir }}" + state: absent + delegate_to: localhost + become: false + + handlers: + - name: reload haproxy + ansible.builtin.systemd: + name: haproxy + state: reloaded diff --git a/inventory/host.ini b/inventory/host.ini index 3afdd9c..146d65d 100644 --- a/inventory/host.ini +++ b/inventory/host.ini @@ -23,7 +23,7 @@ s3-uber ansible_host=80.96.58.164 ansible_user=omnex ansible_ssh_private_key_fil vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.1 [load-balancer] -load-balancer ansible_host=93.185.166.221 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex +load-balancer ansible_host=93.185.166.221 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.12 [infra] monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.2 diff --git a/inventory/host_vars/load-balancer.yml b/inventory/host_vars/load-balancer.yml new file mode 100644 index 0000000..d220790 --- /dev/null +++ b/inventory/host_vars/load-balancer.yml @@ -0,0 +1,6 @@ +# Domaine servi par le load-balancer HAProxy — utilisé par infra/playbook-haproxy.yml +# pour nommer le certificat combiné. Doit correspondre au nom codé en dur dans +# templates/haproxy.cfg.j2 (bind *:443 ssl crt /etc/haproxy/certs/mln-uber.club.pem). +# Le certificat lui-même n'est pas généré ici : il est copié depuis prod-uber +# (voir infra/playbook-haproxy.yml, play "Récupérer le certificat..."). +domain_name: "mln-uber.club"