client_unbound : résolveur local (DNS dynamique) — éprouvé sur un nœud
Unbound par nœud (127.0.0.1) : stub-zone vers PowerDNS (interne) + récursion Internet (ou forward). Alternative dynamique au plancher /etc/hosts. Bascule resolv.conf protégée (apply+confirm) ET validée avant (Unbound doit résoudre interne + Internet, sinon pas de bascule → nœud jamais coupé). Sûr par défaut (apply=false). Prouvé sur data-sql-01 : dig @127.0.0.1 keycloak/id-sso-01 → PowerDNS, deb.debian.org → récursion, apt OK. Rollout flotte = opt-in par nœud. Note : OPNsense embarque Unbound → l'Unbound réseau pourra vivre sur l'appliance de bordure (nœud public, Étape B) ; ce rôle reste complémentaire. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a870c9befe
commit
fa945de5a7
6 changed files with 156 additions and 0 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -24,6 +24,17 @@
|
|||
fermée : déclarer `expose` → **vhost + SAN cert + A PowerDNS + alias plancher**, tout dérivé.
|
||||
Bugs corrigés en chemin : `serveur_loki` (groupe `loki` manquant), `stat` sur cible→contrôle,
|
||||
`become` inutile sur le contrôle.
|
||||
- **Rôle `client_unbound` — résolveur local (DNS dynamique) : éprouvé sur un nœud.** Unbound par
|
||||
nœud (`127.0.0.1`) avec **stub-zone** vers l'autoritatif interne (PowerDNS) + **récursion**
|
||||
Internet (ou forward via `client_unbound_transitaires`). Alternative *dynamique* au plancher
|
||||
`/etc/hosts` statique, sans casser Internet. Bascule de `/etc/resolv.conf` **protégée**
|
||||
(`client_unbound_apply` + `client_unbound_confirm`) **et validée AVANT** (Unbound doit résoudre
|
||||
interne + Internet, sinon pas de bascule → nœud jamais coupé). **Prouvé sur data-sql-01** (rayon
|
||||
d'impact minimal) : `dig @127.0.0.1 keycloak/id-sso-01.lab.chezlepro.internal` → PowerDNS,
|
||||
`deb.debian.org` → récursion, `apt` OK. Rôle sûr par défaut (`apply: false` : installe Unbound
|
||||
sans toucher au resolver). Rollout flotte = opt-in par nœud. **Note direction** : OPNsense
|
||||
embarque Unbound → à terme, l'Unbound *réseau* peut vivre sur l'appliance de bordure (nœud
|
||||
public, Étape B) ; le rôle par-nœud reste portable et complémentaire (cache local).
|
||||
- **Bindings — Phase 1 : résolveur de liens dans `instancier.py` (relations service→service
|
||||
déclaratives).** Une application déclare ses `liens: [{vers, role}]` dans
|
||||
`plan/applications.yml` ; chaque rôle décrit les liens qu'il accepte dans `meta/liens.yml`
|
||||
|
|
|
|||
15
playbooks/groupes/client_unbound.yml
Normal file
15
playbooks/groupes/client_unbound.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
- name: Appliquer le groupe client_unbound
|
||||
hosts: client_unbound
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Vérifier que la cible est Debian
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.distribution == "Debian"
|
||||
fail_msg: "Ce playbook est prévu pour Debian."
|
||||
|
||||
roles:
|
||||
- client_unbound
|
||||
24
roles/client_unbound/defaults/main.yml
Normal file
24
roles/client_unbound/defaults/main.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
# Résolveur local Unbound par nœud : cache + récursion (ou forward), avec une
|
||||
# stub-zone vers l'autoritatif interne (PowerDNS). Rend la résolution dynamique
|
||||
# (au lieu du plancher /etc/hosts statique) sans casser Internet. La bascule de
|
||||
# /etc/resolv.conf est PROTÉGÉE (apply + confirm), comme client_dns.
|
||||
client_unbound_paquets:
|
||||
- unbound
|
||||
- bind9-dnsutils # dig, pour valider avant de basculer le resolver
|
||||
client_unbound_service: "unbound"
|
||||
client_unbound_ecoute: "127.0.0.1"
|
||||
|
||||
# Zone interne autoritative (PowerDNS) — résolue via une stub-zone.
|
||||
client_unbound_zone_interne: "{{ domaine_interne }}"
|
||||
# IP du serveur autoritatif interne. Dérivée du groupe serveur_powerdns.
|
||||
client_unbound_dns_autoritatif: "{{ hostvars[groups['serveur_powerdns'][0]].ansible_host | default('') if groups.get('serveur_powerdns') else '' }}"
|
||||
|
||||
# Transitaires pour '.' (Internet). Vide => Unbound recurse lui-même (racine + DNSSEC).
|
||||
# À renseigner si la récursion sortante est bloquée (forward vers l'upstream réseau).
|
||||
client_unbound_transitaires: []
|
||||
|
||||
# Bascule de /etc/resolv.conf vers 127.0.0.1 : les DEUX requis (garde-fou anti-coupure).
|
||||
client_unbound_apply: false
|
||||
client_unbound_confirm: false
|
||||
client_unbound_resolv_conf: "/etc/resolv.conf"
|
||||
6
roles/client_unbound/handlers/main.yml
Normal file
6
roles/client_unbound/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: Redémarrer unbound
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ client_unbound_service }}"
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
75
roles/client_unbound/tasks/main.yml
Normal file
75
roles/client_unbound/tasks/main.yml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
- name: Exiger l'IP de l'autoritatif interne (PowerDNS)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- client_unbound_dns_autoritatif | length > 0
|
||||
fail_msg: "client_unbound_dns_autoritatif requis (aucun hôte serveur_powerdns actif ?)."
|
||||
|
||||
- name: Installer Unbound
|
||||
ansible.builtin.apt:
|
||||
name: "{{ client_unbound_paquets }}"
|
||||
state: present
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Déployer la configuration Unbound (stub-zone interne + récursion)
|
||||
ansible.builtin.template:
|
||||
src: setops.conf.j2
|
||||
dest: /etc/unbound/unbound.conf.d/setops.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Redémarrer unbound
|
||||
|
||||
- name: Valider la configuration Unbound
|
||||
ansible.builtin.command: unbound-checkconf
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Activer et démarrer Unbound
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ client_unbound_service }}"
|
||||
enabled: true
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Appliquer les redémarrages Unbound avant toute validation/bascule
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
# --- Bascule du resolver : PROTÉGÉE + validée (ne casse jamais un nœud) ---
|
||||
- name: Refuser la bascule du resolver sans confirmation explicite
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- client_unbound_confirm | bool
|
||||
fail_msg: "Bascule refusée : définir client_unbound_confirm=true (avec client_unbound_apply=true)."
|
||||
when: client_unbound_apply | bool
|
||||
|
||||
- name: Valider qu'Unbound résout AVANT de basculer (interne + Internet)
|
||||
ansible.builtin.command: "dig @127.0.0.1 {{ item.nom }} {{ item.type }} +short"
|
||||
register: client_unbound_validation
|
||||
changed_when: false
|
||||
failed_when: client_unbound_validation.stdout | trim == ''
|
||||
loop:
|
||||
- { nom: "{{ client_unbound_zone_interne }}", type: "SOA" }
|
||||
- { nom: "deb.debian.org", type: "A" }
|
||||
loop_control:
|
||||
label: "{{ item.nom }} {{ item.type }}"
|
||||
when:
|
||||
- client_unbound_apply | bool
|
||||
- client_unbound_confirm | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Basculer /etc/resolv.conf vers Unbound local (127.0.0.1)
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ client_unbound_resolv_conf }}"
|
||||
content: |
|
||||
# Généré par Set-OPS (rôle client_unbound). Résolveur local Unbound.
|
||||
search {{ client_unbound_zone_interne }}
|
||||
nameserver 127.0.0.1
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- client_unbound_apply | bool
|
||||
- client_unbound_confirm | bool
|
||||
- not ansible_check_mode
|
||||
25
roles/client_unbound/templates/setops.conf.j2
Normal file
25
roles/client_unbound/templates/setops.conf.j2
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Géré par Set-OPS (rôle client_unbound). Ne pas éditer à la main.
|
||||
# Résolveur local : cache + récursion (ou forward), stub-zone vers l'autoritatif interne.
|
||||
server:
|
||||
interface: {{ client_unbound_ecoute }}
|
||||
access-control: 127.0.0.0/8 allow
|
||||
hide-identity: yes
|
||||
hide-version: yes
|
||||
prefetch: yes
|
||||
do-ip6: no
|
||||
# La zone interne n'est pas signée DNSSEC (PowerDNS autoritatif, dnssec off).
|
||||
domain-insecure: "{{ client_unbound_zone_interne }}"
|
||||
|
||||
# Zone interne : déléguée à l'autoritatif (PowerDNS).
|
||||
stub-zone:
|
||||
name: "{{ client_unbound_zone_interne }}"
|
||||
stub-addr: {{ client_unbound_dns_autoritatif }}
|
||||
{% if client_unbound_transitaires | length > 0 %}
|
||||
|
||||
# Internet : forward vers l'upstream réseau (au lieu de la récursion directe).
|
||||
forward-zone:
|
||||
name: "."
|
||||
{% for t in client_unbound_transitaires %}
|
||||
forward-addr: {{ t }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
Loading…
Reference in a new issue