Set-OPS-Public/playbooks/maintenance/devis-frontiere.yml

119 lines
4.9 KiB
YAML
Raw Normal View History

---
# Devis d'etancheite de la frontiere — RELEVE seul. Ne modifie rien (D-23).
#
# Question posee : la frontiere laisse-t-elle passer ce que la politique n'autorise PAS ?
# `devis-expositions.yml` pose la question inverse — chaque exposition declaree repond-elle.
# Les deux sont necessaires : un pare-feu peut servir tout ce qu'on lui demande *et*
# laisser passer tout le reste.
#
# LES CIBLES SONT LES PORTS REELLEMENT EN ECOUTE, releves ici. Sonder un port ferme ne
# prouverait rien du pare-feu : le refus viendrait de la machine, pas de la bordure.
#
# LE CONTROLE D'ABORD. La sonde interroge une adresse ou personne n'ecoute avant tout
# verdict ; si elle repond, le releve est declare NUL. C'est la lecon du 2026-08-09 :
# `connect()` a menti deux jours durant a travers un `synproxy` herite.
#
# make frontiere-mesurer
- name: Relevé des ports réellement en écoute
hosts: hotes_actifs
become: true
gather_facts: false
tasks:
- name: Recenser les ports TCP en écoute
ansible.builtin.shell:
executable: /bin/bash
# `|| true` en fin de chaîne : avec `pipefail`, un `grep -v` qui ne garde rien
# tuerait la tâche. Un hôte sans écoute publique est un relevé vide, pas une panne.
cmd: >-
set -o pipefail;
ss -ltnH | awk '{print $4}'
| grep -vE '^127\.|^\[::1\]' | sed 's/.*://' | sort -un | tr '\n' ' ' || true
register: devis_frontiere_ss
changed_when: false
- name: Retenir l'écoute de cet hôte
ansible.builtin.set_fact:
devis_frontiere_ecoute:
ip: "{{ ansible_host }}"
ports: "{{ devis_frontiere_ss.stdout.split() | map('int') | list }}"
- name: Sondage depuis les deux points de vue
hosts: localhost
connection: local
become: false
gather_facts: false
tasks:
- name: Rassembler l'écoute de toute la flotte
ansible.builtin.set_fact:
devis_frontiere_ports: >-
{{ dict(groups['hotes_actifs']
| map('extract', hostvars, 'devis_frontiere_ecoute')
| list | zip(groups['hotes_actifs']) | map('reverse') | list) }}
# Forme `argv` et non `cmd` : le chemin du dépôt peut contenir une espace, et `cmd`
# découpe dessus. Mesuré ici même — « /home/.../Espace Chezlepro/... » cassait.
- name: Dériver les cibles à sonder depuis la politique
ansible.builtin.command:
argv:
- python3
- "{{ playbook_dir }}/../../scripts/devis_frontiere.py"
- --cibles
stdin: "{{ devis_frontiere_ports | to_json }}"
register: devis_frontiere_cibles
changed_when: false
- name: Sonder depuis le VLAN d'administration (le poste de l'exploitant)
ansible.builtin.command:
argv:
- python3
- "{{ playbook_dir }}/../../scripts/sonde_tcp.py"
stdin: >-
{{ {'controle': (devis_frontiere_cibles.stdout | from_json).controle,
'cibles': (devis_frontiere_cibles.stdout | from_json).admin} | to_json }}
register: devis_frontiere_admin
changed_when: false
# Play distinct, et ce n'est pas cosmétique. Dans un play `connection: local`, un
# `delegate_to` hérite de cette connexion : la sonde « tenant » s'exécutait en réalité sur
# le poste. Elle rendait donc la frontière JOIGNABLE — depuis le VLAN d'administration,
# elle l'est. Mesuré et corrigé le 2026-08-09 ; c'est la même famille de piège que le
# `connect()` : vérifier d'où l'instrument mesure avant de croire ce qu'il dit.
- name: Sondage depuis un hôte du tenant (le sens sortant)
# Un seul hôte suffit : le devis mesure la bordure, pas les hôtes. Les règles sortantes
# portent sur l'alias du tenant, donc sur tous ses hôtes indistinctement.
# Motif `groupe[0]` et non un template : un `hosts:` templaté échoue quand l'inventaire
# est vide — c'est ce qui cassait `ansible-lint`, qui s'exécute sans inventaire.
hosts: hotes_actifs[0]
become: false
gather_facts: false
tasks:
- name: Sonder les destinations internes interdites
ansible.builtin.script:
cmd: >-
"{{ playbook_dir }}/../../scripts/sonde_tcp.py"
'{{ {"controle": (hostvars["localhost"].devis_frontiere_cibles.stdout
| from_json).controle,
"cibles": (hostvars["localhost"].devis_frontiere_cibles.stdout
| from_json).tenant} | to_json }}'
args:
executable: python3
register: devis_frontiere_tenant
changed_when: false
- name: Déposer le relevé sur le contrôleur
ansible.builtin.copy:
dest: "{{ playbook_dir }}/../../instance/devis-frontiere.json"
mode: "0600"
content: >-
{{ {
'admin': hostvars['localhost'].devis_frontiere_admin.stdout | from_json,
'tenant': devis_frontiere_tenant.stdout | from_json,
'sonde_depuis': inventory_hostname,
} | to_nice_json }}
delegate_to: localhost
become: false