diff --git a/CHANGELOG.md b/CHANGELOG.md index 355a282..5f210b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 : diff --git a/README.md b/README.md index 206d176..2c49892 100644 --- a/README.md +++ b/README.md @@ -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 : diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index cfd8e43..29e020a 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -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 ; diff --git a/docs/dns-interne.md b/docs/dns-interne.md new file mode 100644 index 0000000..1ecf619 --- /dev/null +++ b/docs/dns-interne.md @@ -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. diff --git a/inventories/production/group_vars/clients_dns.yml b/inventories/production/group_vars/clients_dns.yml new file mode 100644 index 0000000..7d007b8 --- /dev/null +++ b/inventories/production/group_vars/clients_dns.yml @@ -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 diff --git a/inventories/production/group_vars/serveurs_powerdns.yml b/inventories/production/group_vars/serveurs_powerdns.yml new file mode 100644 index 0000000..d351f1e --- /dev/null +++ b/inventories/production/group_vars/serveurs_powerdns.yml @@ -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 }}." diff --git a/playbooks/groupes/clients_dns.yml b/playbooks/groupes/clients_dns.yml index 9bdbaf3..73cf5a9 100644 --- a/playbooks/groupes/clients_dns.yml +++ b/playbooks/groupes/clients_dns.yml @@ -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 diff --git a/playbooks/groupes/serveurs_powerdns.yml b/playbooks/groupes/serveurs_powerdns.yml index d8ddc1b..334b69c 100644 --- a/playbooks/groupes/serveurs_powerdns.yml +++ b/playbooks/groupes/serveurs_powerdns.yml @@ -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 diff --git a/roles/clients_dns/README.md b/roles/clients_dns/README.md new file mode 100644 index 0000000..bf7943c --- /dev/null +++ b/roles/clients_dns/README.md @@ -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`. diff --git a/roles/clients_dns/defaults/main.yml b/roles/clients_dns/defaults/main.yml new file mode 100644 index 0000000..40b1af8 --- /dev/null +++ b/roles/clients_dns/defaults/main.yml @@ -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" diff --git a/roles/clients_dns/tasks/main.yml b/roles/clients_dns/tasks/main.yml new file mode 100644 index 0000000..406cae1 --- /dev/null +++ b/roles/clients_dns/tasks/main.yml @@ -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 diff --git a/roles/clients_dns/templates/resolv.conf.j2 b/roles/clients_dns/templates/resolv.conf.j2 new file mode 100644 index 0000000..4ee3dd3 --- /dev/null +++ b/roles/clients_dns/templates/resolv.conf.j2 @@ -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 diff --git a/roles/serveurs_powerdns/README.md b/roles/serveurs_powerdns/README.md new file mode 100644 index 0000000..6ea03c3 --- /dev/null +++ b/roles/serveurs_powerdns/README.md @@ -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. diff --git a/roles/serveurs_powerdns/defaults/main.yml b/roles/serveurs_powerdns/defaults/main.yml new file mode 100644 index 0000000..7f17a55 --- /dev/null +++ b/roles/serveurs_powerdns/defaults/main.yml @@ -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 diff --git a/roles/serveurs_powerdns/handlers/main.yml b/roles/serveurs_powerdns/handlers/main.yml new file mode 100644 index 0000000..e8d3103 --- /dev/null +++ b/roles/serveurs_powerdns/handlers/main.yml @@ -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 diff --git a/roles/serveurs_powerdns/tasks/main.yml b/roles/serveurs_powerdns/tasks/main.yml new file mode 100644 index 0000000..1c4dcc7 --- /dev/null +++ b/roles/serveurs_powerdns/tasks/main.yml @@ -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 diff --git a/roles/serveurs_powerdns/templates/bindbackend.conf.j2 b/roles/serveurs_powerdns/templates/bindbackend.conf.j2 new file mode 100644 index 0000000..3e3b573 --- /dev/null +++ b/roles/serveurs_powerdns/templates/bindbackend.conf.j2 @@ -0,0 +1,4 @@ +zone "{{ serveurs_powerdns_zone }}" { + type master; + file "{{ serveurs_powerdns_zone_directory }}/{{ serveurs_powerdns_zone }}.zone"; +}; diff --git a/roles/serveurs_powerdns/templates/chezlepro-bind.conf.j2 b/roles/serveurs_powerdns/templates/chezlepro-bind.conf.j2 new file mode 100644 index 0000000..5fe1698 --- /dev/null +++ b/roles/serveurs_powerdns/templates/chezlepro-bind.conf.j2 @@ -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 %} diff --git a/roles/serveurs_powerdns/templates/zone.db.j2 b/roles/serveurs_powerdns/templates/zone.db.j2 new file mode 100644 index 0000000..acda006 --- /dev/null +++ b/roles/serveurs_powerdns/templates/zone.db.j2 @@ -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 %}