Implementer le premier jalon DNS interne

This commit is contained in:
Daniel Allaire 2026-06-21 21:56:28 -04:00
parent 18513a3bf0
commit 6fd0efd88d
19 changed files with 386 additions and 10 deletions

View file

@ -7,6 +7,11 @@
- Ajout de `scripts/inventory_rules.py` pour centraliser les règles partagées entre la gestion CLI et l'interface locale d'inventaire.
- Ajout de `docs/dependances-groupes.yml` comme registre exploitable des dépendances causales entre groupes.
- Ajout du contrat `serveurs_powerdns` afin de représenter la capacité DNS centrale requise par `clients_dns`.
- Ajout du premier jalon DNS interne :
- rôle `serveurs_powerdns` avec PowerDNS Authoritative et backend BIND ;
- génération de la zone interne depuis l'inventaire actif ;
- rôle `clients_dns` avec validation de zone et modification resolver protégée ;
- variables de production DNS et runbook `docs/dns-interne.md`.
### Modifié
- Renforcement de la validation d'inventaire :

View file

@ -89,6 +89,8 @@ Le catalogue des services et intégrations prévus est dans `docs/catalogue-serv
Les dépendances causales entre groupes sont dans `docs/dependances-groupes.yml`.
Le runbook du DNS interne initial est dans `docs/dns-interne.md`.
La nomenclature des noms de VM et des VMID est dans `docs/nomenclature-vm.md`.
Créer un clone depuis le modèle Debian 13 via l'API Proxmox :

View file

@ -28,7 +28,7 @@ Un service central peut partager un hôte avec d'autres services du même domain
| Forgejo | `serveurs_forgejo` | `playbooks/groupes/serveurs_forgejo.yml` | `forgejo` |
| Sendmail MTA | `serveurs_sendmail` | `playbooks/groupes/serveurs_sendmail.yml` | `sendmail` |
| step-ca | `serveurs_step_ca` | `playbooks/groupes/serveurs_step_ca.yml` | `step_ca` |
| PowerDNS | `serveurs_powerdns` | `playbooks/groupes/serveurs_powerdns.yml` | `powerdns` |
| PowerDNS | `serveurs_powerdns` | `playbooks/groupes/serveurs_powerdns.yml` | `serveurs_powerdns` |
| Redis | `serveurs_redis` | `playbooks/groupes/serveurs_redis.yml` | `redis` |
| NGINX WAF et reverse proxy | `serveurs_nginx` | `playbooks/groupes/serveurs_nginx.yml` | `nginx` |
| Nextcloud | `serveurs_nextcloud` | `playbooks/groupes/serveurs_nextcloud.yml` | `nextcloud` |
@ -53,7 +53,7 @@ Un service central peut partager un hôte avec d'autres services du même domain
| Intégration | Groupe | Playbook | Rôle futur |
| --- | --- | --- | --- |
| Résolution DNS interne | `clients_dns` | `playbooks/groupes/clients_dns.yml` | `client_dns` |
| Résolution DNS interne | `clients_dns` | `playbooks/groupes/clients_dns.yml` | `clients_dns` |
| Confiance PKI / ACME | `clients_pki` | `playbooks/groupes/clients_pki.yml` | `client_pki` |
| Authentification LDAP | `clients_ldap` | `playbooks/groupes/clients_ldap.yml` | `client_ldap` |
| Supervision Icinga | `clients_supervision` | `playbooks/groupes/clients_supervision.yml` | `client_supervision` |
@ -166,6 +166,8 @@ Les groupes clients peuvent être ajoutés aux VM existantes quand le service ce
Les dépendances exécutables sont déclarées dans `docs/dependances-groupes.yml`.
Le runbook DNS initial est dans `docs/dns-interne.md`.
Ce fichier sert à deux usages :
- bloquer le déploiement d'un groupe tant que ses prérequis actifs ne sont pas présents ;

90
docs/dns-interne.md Normal file
View file

@ -0,0 +1,90 @@
# DNS interne Chezlepro
Le service DNS interne est la premiere capacite de plateforme.
## Groupes
```text
serveurs_powerdns -> service DNS central PowerDNS Authoritative
clients_dns -> integration cliente DNS
```
`clients_dns` depend de `serveurs_powerdns` dans `docs/dependances-groupes.yml`.
## Zone initiale
La zone initiale est :
```text
chezlepro.internal
```
Elle est definie dans :
```text
inventories/production/group_vars/serveurs_powerdns.yml
```
## Enregistrement automatique
Le role `serveurs_powerdns` genere la zone a partir de l'inventaire.
Les hotes qui remplissent ces conditions sont ajoutes automatiquement :
```text
hote dans hotes_actifs
ansible_host defini
```
Exemple :
```text
web-01 ansible_host=192.168.12.101
-> web-01.chezlepro.internal A 192.168.12.101
```
Les enregistrements additionnels explicites sont dans `serveurs_powerdns_records`.
## Backend PowerDNS
Le premier jalon utilise le backend BIND de PowerDNS.
Raison :
- pas de dependance prematuree a PostgreSQL ;
- zone lisible et generee par Ansible ;
- idempotence simple ;
- bon point de depart pour la supervision.
Quand `serveurs_postgresql` sera stable, il sera possible de migrer vers un backend SQL si le besoin operationnel le justifie.
## Clients DNS
Le role `clients_dns` valide :
- qu'un serveur `serveurs_powerdns` actif existe ;
- que la zone repond a une requete SOA ;
- que le nom de l'hote existe dans la zone.
La modification du resolver local est protegee :
```yaml
clients_dns_apply: true
clients_dns_confirm: true
```
Sans ces deux variables, le role valide les prerequis mais ne modifie pas `/etc/resolv.conf`.
Cette protection est volontaire : une mauvaise configuration DNS peut couper la resolution de noms.
## Surveillance a prevoir
Les premiers checks utiles :
- service `pdns` actif ;
- port UDP/TCP 53 disponible ;
- SOA de `chezlepro.internal` resoluble ;
- enregistrement `ns1.chezlepro.internal` resoluble ;
- enregistrements des hotes actifs presents ;
- serial de zone attendu ;
- latence de resolution.

View file

@ -0,0 +1,11 @@
---
# Variables de l'intégration cliente DNS.
clients_dns_zone: "chezlepro.internal"
clients_dns_search_domains:
- "{{ clients_dns_zone }}"
# La modification du resolver est protegee. Activer ces deux variables seulement
# lorsque le serveur DNS interne et la zone ont ete verifies.
clients_dns_apply: false
clients_dns_confirm: false

View file

@ -0,0 +1,13 @@
---
# Variables du service DNS interne PowerDNS.
serveurs_powerdns_zone: "chezlepro.internal"
serveurs_powerdns_nameserver: "ns1"
serveurs_powerdns_contact: "hostmaster.chezlepro.internal"
serveurs_powerdns_serial: 2026062201
serveurs_powerdns_inventory_groups:
- hotes_actifs
serveurs_powerdns_records:
- name: "dns"
type: "CNAME"
value: "ns1.{{ serveurs_powerdns_zone }}."

View file

@ -4,7 +4,12 @@
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente DNS reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_dns."
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:
- clients_dns

View file

@ -4,7 +4,12 @@
become: true
gather_facts: true
tasks:
- name: Indiquer que le service PowerDNS reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_powerdns."
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:
- serveurs_powerdns

View file

@ -0,0 +1,16 @@
# Role `clients_dns`
Valide et configure l'integration DNS cliente vers le service PowerDNS interne.
Par defaut, le role valide les prerequis mais ne modifie pas le resolver local.
La modification DNS est volontairement protegee, car une mauvaise configuration
peut couper la resolution de noms.
Pour appliquer la configuration resolver :
```yaml
clients_dns_apply: true
clients_dns_confirm: true
```
Le serveur DNS requis vient du groupe `serveurs_powerdns`.

View file

@ -0,0 +1,9 @@
---
clients_dns_apply: false
clients_dns_confirm: false
clients_dns_zone: "chezlepro.internal"
clients_dns_search_domains:
- "{{ clients_dns_zone }}"
clients_dns_nameservers: []
clients_dns_resolv_conf_path: "/etc/resolv.conf"
clients_dns_resolv_conf_header: "Gere par Ansible Set-OPS - clients_dns"

View file

@ -0,0 +1,61 @@
---
- name: Déterminer les serveurs DNS internes depuis l'inventaire
ansible.builtin.set_fact:
clients_dns_effective_nameservers: >-
{{
clients_dns_nameservers
if (clients_dns_nameservers | length > 0)
else (
groups.get('serveurs_powerdns', [])
| map('extract', hostvars, 'ansible_host')
| select('defined')
| list
)
}}
- name: Vérifier qu'au moins un serveur DNS interne est connu
ansible.builtin.assert:
that:
- clients_dns_effective_nameservers | length > 0
fail_msg: "Aucun serveur DNS interne disponible pour clients_dns."
- name: Installer les outils de diagnostic DNS
ansible.builtin.apt:
name: dnsutils
state: present
update_cache: true
cache_valid_time: 3600
- name: Valider la zone DNS interne sur le serveur primaire
ansible.builtin.command:
cmd: "dig @{{ clients_dns_effective_nameservers[0] }} {{ clients_dns_zone }} SOA +short"
register: clients_dns_soa_check
changed_when: false
failed_when: clients_dns_soa_check.stdout | trim == ""
- name: Refuser la modification DNS sans confirmation explicite
ansible.builtin.assert:
that:
- clients_dns_confirm | bool
fail_msg: "Modification DNS refusee: definir clients_dns_confirm=true pour modifier le resolver."
when: clients_dns_apply | bool
- name: Configurer le resolver client
ansible.builtin.template:
src: resolv.conf.j2
dest: "{{ clients_dns_resolv_conf_path }}"
owner: root
group: root
mode: "0644"
backup: true
when:
- clients_dns_apply | bool
- clients_dns_confirm | bool
- name: Valider la résolution du nom local dans la zone interne
ansible.builtin.command:
cmd: "dig @{{ clients_dns_effective_nameservers[0] }} {{ inventory_hostname }}.{{ clients_dns_zone }} A +short"
register: clients_dns_host_check
changed_when: false
failed_when: clients_dns_host_check.stdout | trim == ""
when: ansible_host is defined

View file

@ -0,0 +1,8 @@
# {{ clients_dns_resolv_conf_header }}
{% for nameserver in clients_dns_effective_nameservers %}
nameserver {{ nameserver }}
{% endfor %}
{% if clients_dns_search_domains | length > 0 %}
search {{ clients_dns_search_domains | join(' ') }}
{% endif %}
options timeout:2 attempts:2 rotate

View file

@ -0,0 +1,17 @@
# Role `serveurs_powerdns`
Installe et configure PowerDNS Authoritative comme service DNS interne initial.
Ce premier jalon utilise le backend BIND et une zone generee par Ansible depuis
l'inventaire. Cela evite de rendre DNS dependant de PostgreSQL avant que le
service `serveurs_postgresql` soit disponible.
Variables principales :
- `serveurs_powerdns_zone`
- `serveurs_powerdns_nameserver`
- `serveurs_powerdns_contact`
- `serveurs_powerdns_records`
Les hotes actifs portant `ansible_host` sont ajoutes automatiquement dans la
zone par le template.

View file

@ -0,0 +1,33 @@
---
serveurs_powerdns_packages:
- pdns-server
- pdns-backend-bind
- bind9-utils
- dnsutils
serveurs_powerdns_service_name: "pdns"
serveurs_powerdns_zone_directory: "/etc/powerdns/zones"
serveurs_powerdns_bind_config: "/etc/powerdns/bindbackend.conf"
serveurs_powerdns_zone: "chezlepro.internal"
serveurs_powerdns_nameserver: "ns1"
serveurs_powerdns_contact: "hostmaster.chezlepro.internal"
serveurs_powerdns_ttl: 3600
serveurs_powerdns_serial: 2026062201
serveurs_powerdns_soa_refresh: 3600
serveurs_powerdns_soa_retry: 900
serveurs_powerdns_soa_expire: 1209600
serveurs_powerdns_soa_minimum: 3600
serveurs_powerdns_listen_addresses:
- "0.0.0.0"
- "::"
serveurs_powerdns_allow_axfr_ips: []
# Enregistrements additionnels geres explicitement.
serveurs_powerdns_records:
- name: "dns"
type: "CNAME"
value: "ns1.{{ serveurs_powerdns_zone }}."
# Groupes dont les hotes actifs avec ansible_host seront ajoutes a la zone.
serveurs_powerdns_inventory_groups:
- hotes_actifs

View file

@ -0,0 +1,12 @@
---
- name: Validate PowerDNS zone
ansible.builtin.command:
cmd: "named-checkzone {{ serveurs_powerdns_zone }} {{ serveurs_powerdns_zone_directory }}/{{ serveurs_powerdns_zone }}.zone"
changed_when: false
listen: Validate and reload PowerDNS
- name: Restart PowerDNS
ansible.builtin.systemd:
name: "{{ serveurs_powerdns_service_name }}"
state: restarted
listen: Validate and reload PowerDNS

View file

@ -0,0 +1,48 @@
---
- name: Installer PowerDNS Authoritative
ansible.builtin.apt:
name: "{{ serveurs_powerdns_packages }}"
state: present
update_cache: true
cache_valid_time: 3600
- name: Créer le répertoire des zones PowerDNS
ansible.builtin.file:
path: "{{ serveurs_powerdns_zone_directory }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Déployer la configuration du backend BIND
ansible.builtin.template:
src: chezlepro-bind.conf.j2
dest: /etc/powerdns/pdns.d/chezlepro-bind.conf
owner: root
group: root
mode: "0644"
notify: Validate and reload PowerDNS
- name: Déployer l'index des zones PowerDNS
ansible.builtin.template:
src: bindbackend.conf.j2
dest: "{{ serveurs_powerdns_bind_config }}"
owner: root
group: root
mode: "0644"
notify: Validate and reload PowerDNS
- name: Générer la zone DNS interne depuis l'inventaire
ansible.builtin.template:
src: zone.db.j2
dest: "{{ serveurs_powerdns_zone_directory }}/{{ serveurs_powerdns_zone }}.zone"
owner: root
group: root
mode: "0644"
notify: Validate and reload PowerDNS
- name: Activer et démarrer PowerDNS
ansible.builtin.systemd:
name: "{{ serveurs_powerdns_service_name }}"
enabled: true
state: started

View file

@ -0,0 +1,4 @@
zone "{{ serveurs_powerdns_zone }}" {
type master;
file "{{ serveurs_powerdns_zone_directory }}/{{ serveurs_powerdns_zone }}.zone";
};

View file

@ -0,0 +1,9 @@
launch+=bind
bind-config={{ serveurs_powerdns_bind_config }}
local-address={{ serveurs_powerdns_listen_addresses | join(',') }}
{% if serveurs_powerdns_allow_axfr_ips | length > 0 %}
allow-axfr-ips={{ serveurs_powerdns_allow_axfr_ips | join(',') }}
disable-axfr=no
{% else %}
disable-axfr=yes
{% endif %}

View file

@ -0,0 +1,26 @@
$ORIGIN {{ serveurs_powerdns_zone }}.
$TTL {{ serveurs_powerdns_ttl }}
@ IN SOA {{ serveurs_powerdns_nameserver }}.{{ serveurs_powerdns_zone }}. {{ serveurs_powerdns_contact }}. (
{{ serveurs_powerdns_serial }}
{{ serveurs_powerdns_soa_refresh }}
{{ serveurs_powerdns_soa_retry }}
{{ serveurs_powerdns_soa_expire }}
{{ serveurs_powerdns_soa_minimum }}
)
@ IN NS {{ serveurs_powerdns_nameserver }}.{{ serveurs_powerdns_zone }}.
{{ serveurs_powerdns_nameserver }} IN A {{ hostvars[groups['serveurs_powerdns'][0]].ansible_host | default('127.0.0.1') }}
{% for record in serveurs_powerdns_records %}
{{ record.name }} IN {{ record.type }} {{ record.value }}
{% endfor %}
{% set emitted = [] %}
{% for group_name in serveurs_powerdns_inventory_groups %}
{% for host_name in groups.get(group_name, []) | sort %}
{% if hostvars[host_name].ansible_host is defined and host_name not in emitted %}
{{ host_name }} IN A {{ hostvars[host_name].ansible_host }}
{% set _ = emitted.append(host_name) %}
{% endif %}
{% endfor %}
{% endfor %}