From 8f6de3ebe2f5c8a18e4ceef1a4e626ee9ef4a185 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 22 Jun 2026 18:06:11 -0400 Subject: [PATCH] Construire l'ecosysteme de services et outiller l'inventaire Registres (source unique): - docs/nomenclature.yml: domaines, VMID, plan d'adressage 10.1.0.0/16 segmente. - docs/bases-donnees.yml: bases applicatives (1 appli -> 1 base -> 1 owner -> 1 DSN). Roles de service: - Reseau/edge/PKI/mail: nginx, step_ca, sendmail. - Donnees/identite: postgresql (consommateur du registre BD), redis, openldap, keycloak. - Observabilite: prometheus, loki, grafana. - Supervision: icinga (coeur; Icinga Web 2 differe). Forge: forgejo. Integrations clientes: - clients_metriques, clients_journaux, clients_pki, clients_ldap, clients_smtp. Inventaire et outillage: - make inventaire-ui: refonte (cartes, theme sombre, onglets, vue Chaine VM->groupes-> playbooks->roles), saisie du provisioning, auto-proposition depuis la nomenclature, deploiement securise (verifier/deployer, jeton anti-CSRF, verrou, mot de passe vault), robustesse reseau (connexions fermees, favicon). - Makefile: cible verifier-deploiement; detection d'un group_vars de production chiffre. - Scission serveurs_web -> serveurs_web_frontaux/dorsaux; migration de l'adressage vers 10.1.x; retrait des hotes de test; planification des hotes; requirements.yml. Chaque role valide en --syntax-check et ansible-lint (profil production). Secrets references depuis Ansible Vault (jamais en clair); roles non testes live. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 2 +- CHANGELOG.md | 39 + Makefile | 88 +- README.md | 16 +- docs/bases-donnees.yml | 46 + docs/catalogue-services.md | 19 + docs/dns-interne.md | 4 +- docs/modeles_vm/debian13-proxmox.md | 2 +- docs/nomenclature-vm.md | 64 +- docs/nomenclature.yml | 42 + docs/procedure-template-debian13-proxmox.md | 16 +- docs/vm-lifecycle.md | 8 +- inventories/lab/hosts.yml | 7 +- .../production/group_vars/clients_ldap.yml | 8 + .../production/group_vars/clients_pki.yml | 11 + .../production/group_vars/clients_smtp.yml | 5 + .../group_vars/serveurs_forgejo.yml | 9 + .../group_vars/serveurs_grafana.yml | 8 + .../production/group_vars/serveurs_icinga.yml | 8 + .../group_vars/serveurs_keycloak.yml | 12 + .../production/group_vars/serveurs_nginx.yml | 9 + .../group_vars/serveurs_openldap.yml | 11 + .../group_vars/serveurs_postgresql.yml | 10 + .../production/group_vars/serveurs_redis.yml | 8 + .../group_vars/serveurs_sendmail.yml | 11 + .../group_vars/serveurs_step_ca.yml | 16 + inventories/production/hosts.yml | 306 +++-- playbooks/groupes/clients_journaux.yml | 13 +- playbooks/groupes/clients_ldap.yml | 13 +- playbooks/groupes/clients_metriques.yml | 13 +- playbooks/groupes/clients_pki.yml | 13 +- playbooks/groupes/clients_smtp.yml | 13 +- playbooks/groupes/serveurs_forgejo.yml | 13 +- playbooks/groupes/serveurs_grafana.yml | 13 +- playbooks/groupes/serveurs_icinga.yml | 13 +- playbooks/groupes/serveurs_keycloak.yml | 13 +- playbooks/groupes/serveurs_loki.yml | 13 +- playbooks/groupes/serveurs_nginx.yml | 13 +- playbooks/groupes/serveurs_openldap.yml | 13 +- playbooks/groupes/serveurs_postgresql.yml | 13 +- playbooks/groupes/serveurs_prometheus.yml | 13 +- playbooks/groupes/serveurs_redis.yml | 13 +- playbooks/groupes/serveurs_sendmail.yml | 13 +- playbooks/groupes/serveurs_step_ca.yml | 13 +- ...veurs_web.yml => serveurs_web_dorsaux.yml} | 8 +- playbooks/groupes/serveurs_web_frontaux.yml | 17 + playbooks/proxmox/README.md | 14 +- requirements.yml | 6 + roles/clients_journaux/README.md | 27 + roles/clients_journaux/defaults/main.yml | 15 + roles/clients_journaux/handlers/main.yml | 6 + roles/clients_journaux/tasks/main.yml | 50 + .../templates/config.alloy.j2 | 16 + roles/clients_ldap/README.md | 35 + roles/clients_ldap/defaults/main.yml | 24 + roles/clients_ldap/handlers/main.yml | 6 + roles/clients_ldap/tasks/main.yml | 40 + roles/clients_ldap/templates/sssd.conf.j2 | 18 + roles/clients_metriques/README.md | 26 + roles/clients_metriques/defaults/main.yml | 9 + roles/clients_metriques/handlers/main.yml | 6 + roles/clients_metriques/tasks/main.yml | 22 + .../templates/default-node-exporter.j2 | 2 + roles/clients_pki/README.md | 43 + roles/clients_pki/defaults/main.yml | 24 + roles/clients_pki/handlers/main.yml | 5 + roles/clients_pki/tasks/main.yml | 104 ++ .../templates/cert-renewer@.service.j2 | 20 + .../templates/cert-renewer@.timer.j2 | 12 + .../templates/smallstep.sources.j2 | 5 + roles/clients_smtp/README.md | 29 + roles/clients_smtp/defaults/main.yml | 12 + roles/clients_smtp/tasks/main.yml | 15 + roles/clients_smtp/templates/msmtprc.j2 | 9 + roles/serveurs_forgejo/README.md | 34 + roles/serveurs_forgejo/defaults/main.yml | 30 + roles/serveurs_forgejo/handlers/main.yml | 7 + roles/serveurs_forgejo/tasks/main.yml | 115 ++ roles/serveurs_forgejo/templates/app.ini.j2 | 34 + .../templates/forgejo.service.j2 | 20 + roles/serveurs_grafana/README.md | 33 + roles/serveurs_grafana/defaults/main.yml | 22 + roles/serveurs_grafana/handlers/main.yml | 7 + roles/serveurs_grafana/tasks/main.yml | 68 + .../templates/chezlepro.conf.j2 | 5 + .../templates/datasources.yaml.j2 | 13 + roles/serveurs_icinga/README.md | 33 + roles/serveurs_icinga/defaults/main.yml | 28 + roles/serveurs_icinga/handlers/main.yml | 12 + roles/serveurs_icinga/tasks/main.yml | 90 ++ roles/serveurs_icinga/templates/config.yml.j2 | 11 + roles/serveurs_keycloak/README.md | 42 + roles/serveurs_keycloak/defaults/main.yml | 21 + roles/serveurs_keycloak/handlers/main.yml | 7 + roles/serveurs_keycloak/tasks/main.yml | 119 ++ .../templates/keycloak.conf.j2 | 10 + .../templates/keycloak.env.j2 | 4 + .../templates/keycloak.service.j2 | 24 + roles/serveurs_loki/README.md | 30 + roles/serveurs_loki/defaults/main.yml | 14 + roles/serveurs_loki/handlers/main.yml | 6 + roles/serveurs_loki/tasks/main.yml | 51 + roles/serveurs_loki/templates/config.yml.j2 | 35 + roles/serveurs_nginx/README.md | 37 + roles/serveurs_nginx/defaults/main.yml | 28 + roles/serveurs_nginx/handlers/main.yml | 12 + roles/serveurs_nginx/tasks/main.yml | 51 + .../templates/99-chezlepro.conf.j2 | 4 + roles/serveurs_nginx/templates/site.conf.j2 | 26 + roles/serveurs_openldap/README.md | 38 + roles/serveurs_openldap/defaults/main.yml | 22 + roles/serveurs_openldap/tasks/main.yml | 59 + roles/serveurs_postgresql/README.md | 55 + roles/serveurs_postgresql/defaults/main.yml | 38 + roles/serveurs_postgresql/handlers/main.yml | 10 + roles/serveurs_postgresql/tasks/main.yml | 134 ++ .../templates/99-chezlepro.conf.j2 | 7 + .../templates/pg_hba.conf.j2 | 11 + roles/serveurs_prometheus/README.md | 31 + roles/serveurs_prometheus/defaults/main.yml | 16 + roles/serveurs_prometheus/handlers/main.yml | 12 + roles/serveurs_prometheus/tasks/main.yml | 31 + .../templates/default-prometheus.j2 | 2 + .../templates/prometheus.yml.j2 | 23 + roles/serveurs_redis/README.md | 30 + roles/serveurs_redis/defaults/main.yml | 18 + roles/serveurs_redis/handlers/main.yml | 6 + roles/serveurs_redis/tasks/main.yml | 37 + .../templates/chezlepro.conf.j2 | 7 + roles/serveurs_sendmail/README.md | 37 + roles/serveurs_sendmail/defaults/main.yml | 31 + roles/serveurs_sendmail/handlers/main.yml | 12 + roles/serveurs_sendmail/tasks/main.yml | 34 + roles/serveurs_sendmail/templates/main.cf.j2 | 31 + roles/serveurs_step_ca/README.md | 41 + roles/serveurs_step_ca/defaults/main.yml | 27 + roles/serveurs_step_ca/handlers/main.yml | 7 + roles/serveurs_step_ca/tasks/main.yml | 108 ++ .../templates/smallstep.sources.j2 | 5 + .../templates/step-ca.service.j2 | 52 + roles/web/README.md | 7 +- scripts/inventory_gui.py | 1126 +++++++++++++---- scripts/inventory_rules.py | 11 + 143 files changed, 4354 insertions(+), 476 deletions(-) create mode 100644 docs/bases-donnees.yml create mode 100644 docs/nomenclature.yml create mode 100644 inventories/production/group_vars/clients_ldap.yml create mode 100644 inventories/production/group_vars/clients_pki.yml create mode 100644 inventories/production/group_vars/clients_smtp.yml create mode 100644 inventories/production/group_vars/serveurs_forgejo.yml create mode 100644 inventories/production/group_vars/serveurs_grafana.yml create mode 100644 inventories/production/group_vars/serveurs_icinga.yml create mode 100644 inventories/production/group_vars/serveurs_keycloak.yml create mode 100644 inventories/production/group_vars/serveurs_nginx.yml create mode 100644 inventories/production/group_vars/serveurs_openldap.yml create mode 100644 inventories/production/group_vars/serveurs_postgresql.yml create mode 100644 inventories/production/group_vars/serveurs_redis.yml create mode 100644 inventories/production/group_vars/serveurs_sendmail.yml create mode 100644 inventories/production/group_vars/serveurs_step_ca.yml rename playbooks/groupes/{serveurs_web.yml => serveurs_web_dorsaux.yml} (52%) create mode 100644 playbooks/groupes/serveurs_web_frontaux.yml create mode 100644 requirements.yml create mode 100644 roles/clients_journaux/README.md create mode 100644 roles/clients_journaux/defaults/main.yml create mode 100644 roles/clients_journaux/handlers/main.yml create mode 100644 roles/clients_journaux/tasks/main.yml create mode 100644 roles/clients_journaux/templates/config.alloy.j2 create mode 100644 roles/clients_ldap/README.md create mode 100644 roles/clients_ldap/defaults/main.yml create mode 100644 roles/clients_ldap/handlers/main.yml create mode 100644 roles/clients_ldap/tasks/main.yml create mode 100644 roles/clients_ldap/templates/sssd.conf.j2 create mode 100644 roles/clients_metriques/README.md create mode 100644 roles/clients_metriques/defaults/main.yml create mode 100644 roles/clients_metriques/handlers/main.yml create mode 100644 roles/clients_metriques/tasks/main.yml create mode 100644 roles/clients_metriques/templates/default-node-exporter.j2 create mode 100644 roles/clients_pki/README.md create mode 100644 roles/clients_pki/defaults/main.yml create mode 100644 roles/clients_pki/handlers/main.yml create mode 100644 roles/clients_pki/tasks/main.yml create mode 100644 roles/clients_pki/templates/cert-renewer@.service.j2 create mode 100644 roles/clients_pki/templates/cert-renewer@.timer.j2 create mode 100644 roles/clients_pki/templates/smallstep.sources.j2 create mode 100644 roles/clients_smtp/README.md create mode 100644 roles/clients_smtp/defaults/main.yml create mode 100644 roles/clients_smtp/tasks/main.yml create mode 100644 roles/clients_smtp/templates/msmtprc.j2 create mode 100644 roles/serveurs_forgejo/README.md create mode 100644 roles/serveurs_forgejo/defaults/main.yml create mode 100644 roles/serveurs_forgejo/handlers/main.yml create mode 100644 roles/serveurs_forgejo/tasks/main.yml create mode 100644 roles/serveurs_forgejo/templates/app.ini.j2 create mode 100644 roles/serveurs_forgejo/templates/forgejo.service.j2 create mode 100644 roles/serveurs_grafana/README.md create mode 100644 roles/serveurs_grafana/defaults/main.yml create mode 100644 roles/serveurs_grafana/handlers/main.yml create mode 100644 roles/serveurs_grafana/tasks/main.yml create mode 100644 roles/serveurs_grafana/templates/chezlepro.conf.j2 create mode 100644 roles/serveurs_grafana/templates/datasources.yaml.j2 create mode 100644 roles/serveurs_icinga/README.md create mode 100644 roles/serveurs_icinga/defaults/main.yml create mode 100644 roles/serveurs_icinga/handlers/main.yml create mode 100644 roles/serveurs_icinga/tasks/main.yml create mode 100644 roles/serveurs_icinga/templates/config.yml.j2 create mode 100644 roles/serveurs_keycloak/README.md create mode 100644 roles/serveurs_keycloak/defaults/main.yml create mode 100644 roles/serveurs_keycloak/handlers/main.yml create mode 100644 roles/serveurs_keycloak/tasks/main.yml create mode 100644 roles/serveurs_keycloak/templates/keycloak.conf.j2 create mode 100644 roles/serveurs_keycloak/templates/keycloak.env.j2 create mode 100644 roles/serveurs_keycloak/templates/keycloak.service.j2 create mode 100644 roles/serveurs_loki/README.md create mode 100644 roles/serveurs_loki/defaults/main.yml create mode 100644 roles/serveurs_loki/handlers/main.yml create mode 100644 roles/serveurs_loki/tasks/main.yml create mode 100644 roles/serveurs_loki/templates/config.yml.j2 create mode 100644 roles/serveurs_nginx/README.md create mode 100644 roles/serveurs_nginx/defaults/main.yml create mode 100644 roles/serveurs_nginx/handlers/main.yml create mode 100644 roles/serveurs_nginx/tasks/main.yml create mode 100644 roles/serveurs_nginx/templates/99-chezlepro.conf.j2 create mode 100644 roles/serveurs_nginx/templates/site.conf.j2 create mode 100644 roles/serveurs_openldap/README.md create mode 100644 roles/serveurs_openldap/defaults/main.yml create mode 100644 roles/serveurs_openldap/tasks/main.yml create mode 100644 roles/serveurs_postgresql/README.md create mode 100644 roles/serveurs_postgresql/defaults/main.yml create mode 100644 roles/serveurs_postgresql/handlers/main.yml create mode 100644 roles/serveurs_postgresql/tasks/main.yml create mode 100644 roles/serveurs_postgresql/templates/99-chezlepro.conf.j2 create mode 100644 roles/serveurs_postgresql/templates/pg_hba.conf.j2 create mode 100644 roles/serveurs_prometheus/README.md create mode 100644 roles/serveurs_prometheus/defaults/main.yml create mode 100644 roles/serveurs_prometheus/handlers/main.yml create mode 100644 roles/serveurs_prometheus/tasks/main.yml create mode 100644 roles/serveurs_prometheus/templates/default-prometheus.j2 create mode 100644 roles/serveurs_prometheus/templates/prometheus.yml.j2 create mode 100644 roles/serveurs_redis/README.md create mode 100644 roles/serveurs_redis/defaults/main.yml create mode 100644 roles/serveurs_redis/handlers/main.yml create mode 100644 roles/serveurs_redis/tasks/main.yml create mode 100644 roles/serveurs_redis/templates/chezlepro.conf.j2 create mode 100644 roles/serveurs_sendmail/README.md create mode 100644 roles/serveurs_sendmail/defaults/main.yml create mode 100644 roles/serveurs_sendmail/handlers/main.yml create mode 100644 roles/serveurs_sendmail/tasks/main.yml create mode 100644 roles/serveurs_sendmail/templates/main.cf.j2 create mode 100644 roles/serveurs_step_ca/README.md create mode 100644 roles/serveurs_step_ca/defaults/main.yml create mode 100644 roles/serveurs_step_ca/handlers/main.yml create mode 100644 roles/serveurs_step_ca/tasks/main.yml create mode 100644 roles/serveurs_step_ca/templates/smallstep.sources.j2 create mode 100644 roles/serveurs_step_ca/templates/step-ca.service.j2 diff --git a/AGENTS.md b/AGENTS.md index 59f0af3..bdb15fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -336,7 +336,7 @@ clients_pki clients_ldap clients_supervision clients_metriques -serveurs_web +serveurs_web_frontaux serveurs_postgresql serveurs_prometheus ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f210b5..087ebb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,38 @@ ## 2026-06-22 ### Ajouté +- Rôle `serveurs_forgejo` (forge Git, Phase 5) : binaire officiel Forgejo (version épinglée + lien symbolique), utilisateur `git`, **base PostgreSQL via le registre** (4e consommateur, entrée `forgejo`), publication derrière `serveurs_nginx` (`HTTP_ADDR=127.0.0.1`, `INSTALL_LOCK`), secrets (BD + `SECRET_KEY` + `INTERNAL_TOKEN` + admin) via Vault, mailer vers `serveurs_sendmail`, compte administrateur initial créé une fois. Détails d'installation tirés de la doc officielle Forgejo. Activation de l'entrée `forgejo` dans `docs/bases-donnees.yml`. Playbook de groupe et `group_vars` production associés. +- Rôle `serveurs_icinga` (supervision active — cœur, Phase 4) : dépôt apt officiel Icinga, `icinga2` + `icingadb` + `icingadb-redis`, `icinga2 api setup` + activation de la fonctionnalité `icingadb`, **base PostgreSQL via le registre** (entrée `icingadb`, import du schéma, mot de passe partagé via Vault), config Icinga DB (BD + Redis). **Icinga Web 2 différé** à une phase dédiée. Détails de configuration Icinga DB paramétrés et signalés comme non vérifiables verbatim (pages doc partiellement indisponibles). Ajout de l'entrée `icingadb` à `docs/bases-donnees.yml`. Playbook de groupe et `group_vars` production associés. +- Rôle `serveurs_redis` (cache / files, paquet Debian) : surcharge **non destructive** de `redis.conf` via `include`, `requirepass` (Vault, car exposé sur le réseau interne), `maxmemory` + politique `allkeys-lru`, écoute localhost + IP interne. Complète le nœud données (data-01) aux côtés de PostgreSQL. Playbook de groupe et `group_vars` production associés. +- Rôle `clients_smtp` (intégration cliente relais mail) : `msmtp` + `msmtp-mta` relaient le courrier sortant (notifications, cron, alertes) vers `serveurs_sendmail`, sans daemon. Dernière intégration cliente dont le serveur central existe. Playbook de groupe et `group_vars` production associés. +- Rôle `clients_ldap` (intégration cliente identité utilisateur) : **SSSD** (NSS + PAM) vers `serveurs_openldap`, activation de `sss` dans `/etc/nsswitch.conf` (passwd/group/shadow), création automatique des répertoires personnels (`mkhomedir`), bind anonyme par défaut (bind dédié possible via Vault). Authentification des utilisateurs centralisée — complète l'identité machine de `clients_pki`. Playbook de groupe et `group_vars` production associés. +- Rôle `clients_pki` (intégration cliente PKI / identité machine) : `step-cli` (dépôt Smallstep), **confiance** dans l'AC interne (`step ca bootstrap --install` → racine dans le magasin système), **certificat d'hôte** via le provisioner, **renouvellement automatique** (unités systemd officielles `cert-renewer@.{service,timer}`, vérification toutes les 15 min). Chaque hôte obtient une identité machine vérifiable et un TLS interne réel (remplace les certificats auto-signés). Secrets (empreinte racine + mot de passe provisioner, partagé avec `serveurs_step_ca`) via Vault. Détails tirés de la doc officielle Smallstep. Playbook de groupe et `group_vars` production associés. +- Rôle `clients_journaux` (intégration cliente journaux) : **Grafana Alloy** (dépôt apt Grafana) lit le journal système (journald) et le pousse vers `serveurs_loki` ; l'utilisateur `alloy` est ajouté au groupe `systemd-journal`. Complète l'observabilité côté client (métriques + journaux). Playbook de groupe associé. +- Rôle `clients_metriques` (intégration cliente métriques) : installe `prometheus-node-exporter` (paquet Debian) sur les VM du groupe ; `serveurs_prometheus` dérive et scrape automatiquement la cible depuis l'inventaire. Première intégration cliente — ferme la boucle d'observabilité côté métriques. Playbook de groupe associé. +- Rôle `serveurs_grafana` (tableaux de bord, dépôt apt officiel Grafana) : datasources Prometheus + Loki **provisionnées automatiquement** (`/etc/grafana/provisioning/datasources/`), domaine/`root_url` et mot de passe admin (Vault) via un **drop-in systemd non destructif** (ne touche pas `grafana.ini` du paquet), inscriptions publiques désactivées, publication derrière `serveurs_nginx`. Clôt la Phase 3 observabilité. Playbook de groupe et `group_vars` production associés. +- Rôle `serveurs_loki` (agrégation de journaux, dépôt apt officiel Grafana) : configuration single-node (stockage `filesystem`, schéma TSDB v13), rétention via compactor (`retention_enabled`), écoute `:3100`. Le dépôt apt Grafana est partagé avec `serveurs_grafana`. Playbook de groupe associé. +- Rôle `serveurs_prometheus` (collecte de métriques, paquet Debian) : début de la Phase 3 observabilité. Config Prometheus avec cibles `node_exporter` **dérivées de l'inventaire** (hôtes actifs de `clients_metriques` → `:9100`, comme la zone DNS), jobs additionnels libres, rétention configurable via `/etc/default/prometheus`, `promtool check config` avant rechargement (handler dans le rôle). Playbook de groupe associé. +- Rôle `serveurs_keycloak` (SSO/IAM, hub d'authentification centralisée) : distribution Quarkus (version en variable, URL release officielle), **premier consommateur de bout en bout du registre de BD** (`serveurs_postgresql` crée la base/compte `keycloak`, le rôle lit la même entrée pour son DSN — mot de passe partagé via `vault_bd_keycloak`), publication derrière `serveurs_nginx` (`proxy-headers=xforwarded`, `http-enabled=true`), secrets (BD + admin bootstrap) dans un `EnvironmentFile 0640`/`no_log`, `kc.sh build` puis service systemd `start --optimized`. Activation de l'entrée `keycloak` dans `docs/bases-donnees.yml`. Détails de configuration tirés de la doc officielle Keycloak. Playbook de groupe et `group_vars` production associés. +- Rôle `serveurs_openldap` (annuaire interne) : slapd Debian configuré via debconf (installation non interactive, backend MDB), base DN dérivée du domaine (`dc=chezlepro,dc=internal`), mot de passe admin **obligatoire depuis Vault**, unités organisationnelles de base (`people`, `groups`) via `community.general.ldap_entry`. Playbook de groupe et `group_vars` production associés. +- Registre déclaratif des bases de données applicatives `docs/bases-donnees.yml` (patron « 1 appli → 1 base → 1 compte propriétaire → 1 chaîne de connexion »). Consommé côté serveur par `serveurs_postgresql` (crée base + compte propriétaire, mot de passe depuis la variable Vault nommée par `secret`) et, côté appli, pour bâtir la chaîne de connexion — secret partagé, source unique. Évite un rôle par base. Vide par défaut. +- Rôle `serveurs_step_ca` (AC interne Smallstep step-ca + provisioner ACME) : dépôt apt officiel Smallstep (clé + source deb822), installation `step-ca`/`step-cli`, utilisateur système `step`, initialisation de l'AC **une seule fois** (`step ca init` standalone + ACME, garde-fou `creates: ca.json`), mots de passe CA et provisioner **obligatoires depuis Vault**, unité systemd durcie officielle. Playbook de groupe et `group_vars` production associés. Clôt la Phase 1 des fondations. Détails d'installation tirés de la documentation officielle Smallstep (non inventés). +- Rôle `serveurs_sendmail` (relais SMTP sortant, basé sur Postfix qui fournit l'interface `sendmail`-compatible) : installation non interactive (debconf), relais limité à `mynetworks` (`10.1.0.0/16`, pas de relais ouvert), smarthost amont optionnel, TLS opportuniste (snakeoil par défaut), `postfix check` avant reload. Playbook de groupe et `group_vars` production associés. +- Rôle `serveurs_nginx` (edge) : reverse proxy et terminaison TLS, certificat auto-signé (snakeoil) par défaut avec chemins en variables pour bascule vers l'AC interne/ACME, durcissement de base (`server_tokens off`, TLS 1.2/1.3), sites de reverse proxy pilotés par variables (vides par défaut), validation `nginx -t` avant reload (handler dans le rôle). Playbook de groupe `serveurs_nginx` et `group_vars` production associés. +- Rôle `serveurs_postgresql` (premier service applicatif après PowerDNS) : PostgreSQL packagé Debian 13, écoute `127.0.0.1` + IP interne, `pg_hba` en `scram-sha-256` ouvert au réseau interne `10.1.0.0/16`, configuration via `conf.d`, provisionnement optionnel de comptes/bases par variables (mots de passe via Vault), handlers reload/restart dans le rôle. Playbook de groupe `serveurs_postgresql` et `group_vars` production associés. Ajout de `requirements.yml` (collections `community.postgresql`, `community.general`). +- Vue des liens VM → groupes → playbooks → rôles dans `make inventaire-ui` : onglet « Chaîne » par hôte (panneau détail) et vue arbre globale (bascule « Inventaire / Chaîne » en en-tête). Les rôles sont extraits des `playbooks/groupes/*.yml` et exposés dans l'API ; les groupes sans rôle sont signalés « stub ». +- Champ de mot de passe du vault Ansible dans la modale de déploiement de `make inventaire-ui` : transmis via fichier temporaire `0600` + `ANSIBLE_VAULT_PASSWORD_FILE`, supprimé après exécution, jamais journalisé. Évite tout prompt `--ask-vault-pass` bloquant en exécution non-interactive. Le flux vault de `make cloner-vm` / `creer-vm` n'est pas modifié. +- Catégorie « Modèles / essais » (VLAN 99 → `10.1.99.0/24`) dans `docs/nomenclature.yml`. +- Registre de nomenclature machine-lisible `docs/nomenclature.yml` (source unique) : domaines, catégories, plan d'adressage `10.1.0.0/16` segmenté par fonction (un /24 et un VLAN par catégorie), dérivations hostname / VMID / VLAN / IP / passerelle. +- Auto-proposition dans `make inventaire-ui` : bouton « ✨ Proposer » et auto-remplissage à la saisie du nom, qui dérivent VMID, VLAN, IP et passerelle depuis `docs/nomenclature.yml` (avec indice de zone). Migration du plan d'adressage interne vers `10.1.0.0/16` (remplace les essais `192.168.12.x`). +- Adoption du nom officiel « Set-OPS — Votre artisan numérique » : titre et en-tête de `make inventaire-ui`, et titre du `README.md`. +- Déploiement depuis `make inventaire-ui` : boutons « Vérifier » (dry-run `--check --diff`) et « Déployer » (réel) par hôte, avec console de sortie en direct. Garde-fous : jeton anti-CSRF (`X-Jeton`), verrou (une seule exécution à la fois), validation du nom d'hôte (anti-injection), refus si l'hôte n'est pas actif, déploiement bloqué tant qu'une vérification n'a pas réussi (réarmé à chaque sauvegarde), restriction à l'inventaire production. Déploiement non transactionnel (sans rollback) signalé à l'opérateur. +- Cible `make verifier-deploiement HOTE=…` : déploiement à blanc (`--check --diff`) d'un hôte selon ses groupes, réutilisée par l'interface. +- Refonte de l'interface `make inventaire-ui` : thème sombre dense, grille de cartes d'hôtes groupées par état (Actifs / Planifiés) avec badge de statut coloré, chips de filtre (Tous / Actifs / Planifiés / Bloqués), recherche, et panneau détail maître-détail à onglets (Réseau / Proxmox / Groupes) avec tuiles de chiffres-clés (VMID, mémoire, cœurs, disque, groupes), bascule d'état actif/planifié, panneau de dépendances repliable, raccourci Ctrl/Cmd+S. +- Saisie des paramètres de provisioning par hôte dans l'inventaire (variables d'hôte) : `proxmox_cidr`, `proxmox_passerelle`, `proxmox_vlan`, `proxmox_pont`, `proxmox_dns`, `proxmox_noeud`, `proxmox_stockage`, `proxmox_disque_taille`, `proxmox_memoire`, `proxmox_coeurs`, avec validation (VLAN 1-4094, CIDR 0-32, entiers positifs) et round-trip non destructif. Documentées dans `docs/nomenclature-vm.md`. +- Scission de la couche applicative web en deux groupes distincts de l'edge `serveurs_nginx` : + - `serveurs_web_frontaux` (présentation web : UI, rendu, assets) ; + - `serveurs_web_dorsaux` (application web : API, traitement) ; + - playbooks homonymes et entrée dédiée dans `docs/catalogue-services.md`. - Ajout de `make hote-planifier` pour renseigner un hôte, son VMID et ses groupes sans création ni déploiement immédiat. - 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. @@ -14,6 +46,13 @@ - variables de production DNS et runbook `docs/dns-interne.md`. ### Modifié +- Robustesse du serveur `make inventaire-ui` : `repondre()` avale proprement les connexions fermées par le client (`BrokenPipeError`/`ConnectionResetError`) au lieu de déverser une trace dans la console ; ajout d'un favicon (204) pour éviter le 404 répété du navigateur. +- `make deployer` et `make verifier-deploiement` détectent un `group_vars` de production chiffré (Ansible Vault) et demandent le mot de passe **une seule fois** (réutilisé pour tous les playbooks et sous-vérifications via fichier temporaire `0600`, nettoyé en fin de run). Refus propre si vault chiffré détecté en exécution **non-interactive** ; la GUI ferme `stdin` du sous-processus et fournit le mot de passe via la modale, donc elle ne bloque jamais. Ferme l'asymétrie de gestion des secrets entre terminal et GUI. +- Migration complète de l'adressage interne vers `10.1.0.0/16` : golden template `basiqueChezlepro` → `10.1.99.99`, et tous les exemples `192.168.x` (README, Makefile, runbooks Proxmox et template) réécrits vers le schéma `10.1.x` / `web-frontal-01`. +- Remplacement du groupe `serveurs_web` par `serveurs_web_frontaux` et `serveurs_web_dorsaux` dans les inventaires lab et production, mises à jour des exemples `README.md` et `AGENTS.md`. +- Retrait des hôtes de test `web-01` et `web-02` de l'inventaire production. +- Planification de la couche applicative web : `web-frontal-01` (95301), `web-frontal-02` (95302) dans `serveurs_web_frontaux` et `web-dorsal-01` (95401) dans `serveurs_web_dorsaux`, tous en `hotes_planifies` avec socle et durcissement. +- Mise à jour de `docs/nomenclature-vm.md` : domaines `web-frontal` / `web-dorsal`, retrait des noms de test `web-01`/`web-02`. - Renforcement de la validation d'inventaire : - un hôte doit être soit planifié, soit actif, mais pas les deux ; - un hôte actif doit avoir `ansible_host` ; diff --git a/Makefile b/Makefile index 9b374ca..714b761 100644 --- a/Makefile +++ b/Makefile @@ -84,30 +84,32 @@ aide: @printf '%s\n' 'VM' @printf '%s\n' ' Creer une VM complete:' @printf '%s\n' ' make creer-vm \' - @printf '%s\n' ' HOTE=web-01 \' - @printf '%s\n' ' VMID=101 \' - @printf '%s\n' ' VLAN=12 \' - @printf '%s\n' ' ADRESSE_IP=192.168.12.101 \' - @printf '%s\n' ' PASSERELLE=192.168.12.1 \' + @printf '%s\n' ' HOTE=web-frontal-01 \' + @printf '%s\n' ' VMID=95301 \' + @printf '%s\n' ' VLAN=15 \' + @printf '%s\n' ' ADRESSE_IP=10.1.15.31 \' + @printf '%s\n' ' PASSERELLE=10.1.15.1 \' @printf '%s\n' ' GROUPES="serveurs_debian serveurs_durcis"' @printf '%s\n' ' Cloner seulement, sans inventaire:' - @printf '%s\n' ' make cloner-vm HOTE=web-01 VMID=101 VLAN=12 ADRESSE_IP=192.168.12.101 PASSERELLE=192.168.12.1' + @printf '%s\n' ' make cloner-vm HOTE=web-frontal-01 VMID=95301 VLAN=15 ADRESSE_IP=10.1.15.31 PASSERELLE=10.1.15.1' @printf '%s\n' ' Configurer Proxmox et le Vault API:' @printf '%s\n' ' make config' @printf '%s\n' '' @printf '%s\n' 'Hotes' @printf '%s\n' ' Planifier un hote sans le deployer:' @printf '%s\n' ' make hote-planifier HOTE=obs-01 VMID=94101 GROUPES="serveurs_debian serveurs_durcis serveurs_prometheus serveurs_loki"' + @printf '%s\n' ' Verifier un deploiement a blanc (dry-run):' + @printf '%s\n' ' make verifier-deploiement HOTE=web-frontal-01' @printf '%s\n' ' Remettre un hote en conformite selon ses groupes:' - @printf '%s\n' ' make deployer HOTE=web-01' + @printf '%s\n' ' make deployer HOTE=web-frontal-01' @printf '%s\n' ' Afficher un hote:' - @printf '%s\n' ' make hote-afficher HOTE=web-01' + @printf '%s\n' ' make hote-afficher HOTE=web-frontal-01' @printf '%s\n' ' Ajouter un hote existant:' - @printf '%s\n' ' make hote-ajouter HOTE=web-01 VMID=99101 ADRESSE_IP=192.168.12.101 GROUPES="serveurs_debian serveurs_durcis"' + @printf '%s\n' ' make hote-ajouter HOTE=web-frontal-01 VMID=95301 ADRESSE_IP=10.1.15.31 GROUPES="serveurs_debian serveurs_durcis"' @printf '%s\n' ' Modifier ses groupes:' - @printf '%s\n' ' make hote-groupes HOTE=web-01 GROUPES="serveurs_debian serveurs_durcis"' + @printf '%s\n' ' make hote-groupes HOTE=web-frontal-01 GROUPES="serveurs_debian serveurs_durcis"' @printf '%s\n' ' Diagnostiquer:' - @printf '%s\n' ' make verifier-hote LIMITE=web-01' + @printf '%s\n' ' make verifier-hote LIMITE=web-frontal-01' @printf '%s\n' '' @printf '%s\n' 'Groupes' @printf '%s\n' ' Appliquer un groupe complet:' @@ -140,8 +142,8 @@ aide: @printf '%s\n' ' make verifier' @printf '%s\n' '' @printf '%s\n' 'Variables frequentes' - @printf '%s\n' ' HOTE=web-01 GROUPE=serveurs_debian GROUPES="serveurs_debian serveurs_durcis"' - @printf '%s\n' ' VMID=101 VLAN=12 ADRESSE_IP=192.168.12.101 PASSERELLE=192.168.12.1' + @printf '%s\n' ' HOTE=web-frontal-01 GROUPE=serveurs_debian GROUPES="serveurs_debian serveurs_durcis"' + @printf '%s\n' ' VMID=95301 VLAN=15 ADRESSE_IP=10.1.15.31 PASSERELLE=10.1.15.1' @printf '%s\n' ' FICHIER_INVENTAIRE=inventories/production/hosts.yml FICHIER_DEPENDANCES=docs/dependances-groupes.yml CONFIRMER=true' .PHONY: lint @@ -230,23 +232,35 @@ appliquer: ansible-runtime ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)' deployer: - @if [[ -z "$(HOTE)" ]]; then \ + @set -e; \ + if [[ -z "$(HOTE)" ]]; then \ printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \ exit 2; \ - fi - @python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES) > /dev/null - @python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) verifier-actif --hote $(HOTE) - @python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-hote --hote $(HOTE) - $(MAKE) _verifier-acces-hote LIMITE="$(HOTE)" - $(MAKE) _verifier-privileges-hote LIMITE="$(HOTE)" - @playbooks="$$(python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES))" || exit $$?; \ + fi; \ + python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) verifier-actif --hote $(HOTE); \ + python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-hote --hote $(HOTE); \ + playbooks="$$(python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES))"; \ if [[ -z "$$playbooks" ]]; then \ printf '%s\n' 'Refus: aucun playbook applicable pour HOTE=$(HOTE).'; \ exit 2; \ fi; \ + vault_chiffre="$$(grep -rlsIF '$$ANSIBLE_VAULT' inventories/production/group_vars 2>/dev/null | head -1 || true)"; \ + if [[ -n "$$vault_chiffre" && -z "$${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then \ + if [[ -t 0 ]]; then \ + read -r -s -p 'Mot de passe du vault Ansible: ' mdp; echo; \ + vf="$$(mktemp)"; printf '%s' "$$mdp" > "$$vf"; chmod 600 "$$vf"; \ + export ANSIBLE_VAULT_PASSWORD_FILE="$$vf"; \ + trap 'rm -f "$$vf"' EXIT; \ + else \ + printf '%s\n' 'Refus: vault chiffre detecte mais aucun mot de passe (entree non interactive). Fournir ANSIBLE_VAULT_PASSWORD_FILE ou le champ vault de la GUI.'; \ + exit 2; \ + fi; \ + fi; \ + $(MAKE) _verifier-acces-hote LIMITE="$(HOTE)"; \ + $(MAKE) _verifier-privileges-hote LIMITE="$(HOTE)"; \ for playbook in $$playbooks; do \ ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)"; \ - done + done; \ $(MAKE) verifier-hote LIMITE="$(HOTE)" deployer-groupe: @@ -256,6 +270,36 @@ deployer-groupe: fi $(MAKE) appliquer GROUPE="$(GROUPE)" +.PHONY: verifier-deploiement +verifier-deploiement: ansible-runtime + @set -e; \ + if [[ -z "$(HOTE)" ]]; then \ + printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \ + exit 2; \ + fi; \ + python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) verifier-actif --hote $(HOTE); \ + python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-hote --hote $(HOTE); \ + playbooks="$$(python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES))"; \ + if [[ -z "$$playbooks" ]]; then \ + printf '%s\n' 'Refus: aucun playbook applicable pour HOTE=$(HOTE).'; \ + exit 2; \ + fi; \ + vault_chiffre="$$(grep -rlsIF '$$ANSIBLE_VAULT' inventories/production/group_vars 2>/dev/null | head -1 || true)"; \ + if [[ -n "$$vault_chiffre" && -z "$${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then \ + if [[ -t 0 ]]; then \ + read -r -s -p 'Mot de passe du vault Ansible: ' mdp; echo; \ + vf="$$(mktemp)"; printf '%s' "$$mdp" > "$$vf"; chmod 600 "$$vf"; \ + export ANSIBLE_VAULT_PASSWORD_FILE="$$vf"; \ + trap 'rm -f "$$vf"' EXIT; \ + else \ + printf '%s\n' 'Refus: vault chiffre detecte mais aucun mot de passe (entree non interactive). Fournir ANSIBLE_VAULT_PASSWORD_FILE ou le champ vault de la GUI.'; \ + exit 2; \ + fi; \ + fi; \ + for playbook in $$playbooks; do \ + ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)" --check --diff; \ + done + cloner-vm: ansible-runtime @if [[ -z "$(HOTE)" || -z "$(VMID)" ]]; then \ printf '%s\n' 'Refus: relancer avec HOTE=nom VMID=id_clone.'; \ diff --git a/README.md b/README.md index 2c49892..c066067 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Set-OPS +# Set-OPS — Votre artisan numérique Dépôt Ansible central pour construire, configurer, maintenir et documenter les systèmes de Chezlepro Inc. @@ -57,7 +57,7 @@ Inspecter les inventaires : ```bash make inventaire -make hote-afficher HOTE=web-01 +make hote-afficher HOTE=web-frontal-01 ``` Ouvrir l'interface locale de gestion d'inventaire : @@ -75,15 +75,15 @@ make hote-planifier HOTE=obs-01 VMID=94101 GROUPES="serveurs_debian serveurs_dur Ajouter une VM à l'inventaire production et l'associer aux groupes qui détermineront sa configuration : ```bash -make hote-ajouter HOTE=web-01 VMID=99101 ADRESSE_IP=192.168.12.101 GROUPES="serveurs_debian serveurs_durcis" -make hote-groupes HOTE=web-01 GROUPES="serveurs_debian serveurs_durcis" +make hote-ajouter HOTE=web-frontal-01 VMID=95301 ADRESSE_IP=10.1.15.31 GROUPES="serveurs_debian serveurs_durcis" +make hote-groupes HOTE=web-frontal-01 GROUPES="serveurs_debian serveurs_durcis" ``` Chaque groupe opérationnel doit avoir son playbook homonyme dans `playbooks/groupes/`. La conformité normale des VM passe par ces playbooks de groupes. Les rôles de socle et de durcissement sont appliqués par `serveurs_debian` et `serveurs_durcis`, pas par des playbooks de couches séparés. -Les groupes opérationnels avec des hôtes, comme `serveurs_web` ou `clients_dns`, sont validés contre `playbooks/groupes/` par les commandes d'inventaire. +Les groupes opérationnels avec des hôtes, comme `serveurs_web_frontaux` ou `clients_dns`, sont validés contre `playbooks/groupes/` par les commandes d'inventaire. Le catalogue des services et intégrations prévus est dans `docs/catalogue-services.md`. @@ -97,8 +97,8 @@ Créer un clone depuis le modèle Debian 13 via l'API Proxmox : ```bash make config -make creer-vm HOTE=web-01 VMID=101 VLAN=12 ADRESSE_IP=192.168.12.101 PASSERELLE=192.168.12.1 GROUPES="serveurs_debian serveurs_durcis" -make deployer HOTE=web-01 +make creer-vm HOTE=web-frontal-01 VMID=95301 VLAN=15 ADRESSE_IP=10.1.15.31 PASSERELLE=10.1.15.1 GROUPES="serveurs_debian serveurs_durcis" +make deployer HOTE=web-frontal-01 ``` Les valeurs Cloud-Init communes déjà présentes dans le modèle Proxmox sont héritées par les clones. @@ -119,7 +119,7 @@ make nettoyer-modele CONFIRMER=true Déployer ou remettre en conformité une VM Debian clonée depuis le template : ```bash -make deployer HOTE=web-01 +make deployer HOTE=web-frontal-01 ``` Déployer ou remettre en conformité un groupe : diff --git a/docs/bases-donnees.yml b/docs/bases-donnees.yml new file mode 100644 index 0000000..b4b5ec9 --- /dev/null +++ b/docs/bases-donnees.yml @@ -0,0 +1,46 @@ +--- +# Registre des bases de donnees applicatives Set-OPS. +# +# Patron : 1 appli -> 1 base nommee -> 1 compte proprietaire (db_owner) -> 1 chaine de connexion. +# +# Deux consommateurs du MEME registre : +# - serveurs_postgresql : cree chaque base + son compte proprietaire +# (mot de passe = la variable Vault nommee par le champ "secret"). +# - le role applicatif : construit sa chaine de connexion vers cette base. +# +# Le mot de passe est PARTAGE : il vit une seule fois dans le Vault (variable nommee +# par "secret"), lu par le serveur (pour creer le compte) et par l'appli (pour se +# connecter). Source unique, jamais de desynchronisation. +# +# Convention de chaine de connexion cote appli : +# host= port=5432 dbname= user= +# password={{ lookup('vars', ) }} sslmode=require +# L'IP du serveur BD se derive de la nomenclature (ex. data-01 -> 10.1.13.11). +# +# Prerequis : la dependance "appli requiert serveurs_postgresql actif" est declaree +# dans docs/dependances-groupes.yml (la base existe avant que l'appli s'y connecte). +# +# Ajouter une appli = une entree ici + son secret dans le Vault. Aucun nouveau role. + +bases_donnees: + keycloak: + base: keycloak + proprietaire: keycloak + serveur: serveurs_postgresql + secret: vault_bd_keycloak + icingadb: + base: icingadb + proprietaire: icingadb + serveur: serveurs_postgresql + secret: vault_bd_icingadb +# icingaweb : a ajouter avec la phase Icinga Web 2 (2e base de l'ecosysteme Icinga). + forgejo: + base: forgejo + proprietaire: forgejo + serveur: serveurs_postgresql + secret: vault_bd_forgejo +# nextcloud: +# base: nextcloud +# proprietaire: nextcloud +# serveur: serveurs_postgresql +# secret: vault_bd_nextcloud diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index 29e020a..4ba0630 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -34,6 +34,25 @@ Un service central peut partager un hôte avec d'autres services du même domain | Nextcloud | `serveurs_nextcloud` | `playbooks/groupes/serveurs_nextcloud.yml` | `nextcloud` | | Collabora | `serveurs_collabora` | `playbooks/groupes/serveurs_collabora.yml` | `collabora` | +## Couche applicative web + +La couche applicative web est distincte de l'edge `serveurs_nginx` : + +- `serveurs_nginx` est l'edge : mandataire inverse, terminaison TLS, WAF ; il reçoit le trafic externe et le route. +- `serveurs_web_frontaux` est la couche présentation web (UI, rendu, assets), servie *derrière* l'edge. +- `serveurs_web_dorsaux` est la couche application web (API, traitement), consommée par les frontaux. + +Le « web » de ces deux groupes est implicite par leur position derrière `serveurs_nginx`. Le jour où un service dorsal n'est pas web (worker batch, file, démon), créer un groupe dédié plutôt que d'élargir `serveurs_web_dorsaux`. + +| Couche | Groupe | Playbook | Rôle futur | +| --- | --- | --- | --- | +| Présentation web (frontends) | `serveurs_web_frontaux` | `playbooks/groupes/serveurs_web_frontaux.yml` | `web_frontaux` | +| Application web (backends) | `serveurs_web_dorsaux` | `playbooks/groupes/serveurs_web_dorsaux.yml` | `web_dorsaux` | + +Hôtes planifiés : `web-frontal-01` et `web-frontal-02` dans `serveurs_web_frontaux` ; `web-dorsal-01` dans `serveurs_web_dorsaux`. Aucun n'est encore actif (à créer puis activer). + +Dépendance d'intégration prévue : `serveurs_web_frontaux` devra publier via `serveurs_nginx` (règle « tout service exposé en HTTP(S) passe par l'edge »). Cette dépendance n'est pas encore déclarée dans `docs/dependances-groupes.yml` (les deux groupes sont vides) ; elle sera ajoutée avec le rôle `web_frontaux`, lorsque des frontaux actifs devront publier via l'edge. + ## Hôtes planifiés | Hôte | Services | diff --git a/docs/dns-interne.md b/docs/dns-interne.md index 1ecf619..74dc874 100644 --- a/docs/dns-interne.md +++ b/docs/dns-interne.md @@ -39,8 +39,8 @@ ansible_host defini Exemple : ```text -web-01 ansible_host=192.168.12.101 --> web-01.chezlepro.internal A 192.168.12.101 +web-frontal-01 ansible_host=10.1.15.31 +-> web-frontal-01.chezlepro.internal A 10.1.15.31 ``` Les enregistrements additionnels explicites sont dans `serveurs_powerdns_records`. diff --git a/docs/modeles_vm/debian13-proxmox.md b/docs/modeles_vm/debian13-proxmox.md index 5df993a..2f3f228 100644 --- a/docs/modeles_vm/debian13-proxmox.md +++ b/docs/modeles_vm/debian13-proxmox.md @@ -61,7 +61,7 @@ VM destinée à devenir un template, pas un serveur de production Vérifications utiles depuis le poste Ansible : ```bash -ssh ansible@192.168.12.99 +ssh ansible@10.1.99.99 ansible -i inventories/lab/hosts.yml modeles_vm -m ping ansible -i inventories/lab/hosts.yml modeles_vm -m setup ``` diff --git a/docs/nomenclature-vm.md b/docs/nomenclature-vm.md index 44d77ee..f6fdd36 100644 --- a/docs/nomenclature-vm.md +++ b/docs/nomenclature-vm.md @@ -25,9 +25,19 @@ obs-01 mon-01 forge-01 collab-01 +web-frontal-01 +web-dorsal-01 ``` -Les noms courts historiques `web-01` et `web-02` restent valides pour les deux premières VM déjà créées. +La couche applicative web suit le même format. Le tier est porté par le domaine, au singulier puisqu'il nomme une instance : + +```text +web-frontal-01 présentation web (UI, rendu, assets), groupe serveurs_web_frontaux +web-frontal-02 +web-dorsal-01 application web (API, traitement), groupe serveurs_web_dorsaux +``` + +Les anciens noms de test `web-01` et `web-02` sont retirés. Ils ne doivent pas être réutilisés comme noms de production : préférer `web-frontal-NN` et `web-dorsal-NN`. ## Regroupements recommandés @@ -43,6 +53,8 @@ Les noms courts historiques `web-01` et `web-02` restent valides pour les deux p | `mon-01` | `serveurs_icinga` | | `forge-01` | `serveurs_forgejo` | | `collab-01` | `serveurs_nextcloud`, `serveurs_collabora` | +| `web-frontal-01`, `web-frontal-02` | `serveurs_web_frontaux` | +| `web-dorsal-01` | `serveurs_web_dorsaux` | Les groupes restent fins et composables. La cohabitation se fait en associant plusieurs groupes au même hôte. @@ -57,6 +69,56 @@ Les groupes restent fins et composables. La cohabitation se fait en associant pl | `95xxx` | applications internes | | `99xxx` | modèles, essais initiaux ou exceptions documentées | +## Plan d'adressage interne + +Réseau interne unique : `10.1.0.0/16`. Segmentation par fonction, un `/24` et un VLAN par catégorie, **3ᵉ octet = VLAN** (L2 alignée sur L3). + +| Catégorie | VLAN | Sous-réseau | Passerelle | +| --- | --- | --- | --- | +| 1 — Fondations / infra | 11 | `10.1.11.0/24` | `10.1.11.1` | +| 2 — Identité | 12 | `10.1.12.0/24` | `10.1.12.1` | +| 3 — Données | 13 | `10.1.13.0/24` | `10.1.13.1` | +| 4 — Observabilité | 14 | `10.1.14.0/24` | `10.1.14.1` | +| 5 — Applications | 15 | `10.1.15.0/24` | `10.1.15.1` | + +Adresse d'hôte (4ᵉ octet) : **`service × 10 + NN`**. `.1` = passerelle ; `.2`–`.9` réservés. Exemple : `web-dorsal-01` (catégorie 5, service 4, NN 01) → `10.1.15.41`. + +Tout se dérive du domaine de l'hôte, et la source unique machine-lisible est **`docs/nomenclature.yml`** : + +```text +hostname = - +VMID = 9 · catégorie · service · NN +VLAN = catégorie.vlan +IP = 10.1..(service × 10 + NN) +``` + +`make inventaire-ui` lit ce registre et **propose** automatiquement VMID, VLAN, IP et passerelle quand on nomme un hôte. La sécurité entre zones se fera par règles inter-zones (nftables / edge), pas par l'adressage. + +Contrainte : `NN` de 01 à 09 par domaine (l'octet hôte reste dans le bloc du service). Au-delà, ouvrir un nouveau domaine/service dans `docs/nomenclature.yml`. + +La segmentation `10.1.0.0/16` remplace l'ancienne plage d'essais `192.168.12.x`. + +## Variables de provisioning d'hôte + +L'inventaire est la source de vérité du provisioning d'une VM. Ces variables d'hôte sont saisissables via `make inventaire-ui` (ou à la main) et décrivent l'instanciation attendue : + +| Variable | Sens | Type | +| --- | --- | --- | +| `ansible_host` | adresse IP | str | +| `proxmox_cidr` | masque réseau en bits (0-32) | int | +| `proxmox_passerelle` | passerelle | str | +| `proxmox_vlan` | tag VLAN (1-4094) | int | +| `proxmox_pont` | pont réseau Proxmox (ex. `vmbr0`) | str | +| `proxmox_dns` | serveurs DNS Cloud-Init (séparés par virgule) | str | +| `proxmox_vmid` | identifiant VM Proxmox | int | +| `proxmox_noeud` | nœud Proxmox cible | str | +| `proxmox_stockage` | stockage du disque | str | +| `proxmox_disque_taille` | taille disque (ex. `32G`) | str | +| `proxmox_memoire` | mémoire en Mo | int | +| `proxmox_coeurs` | nombre de cœurs | int | + +Ces valeurs sont renseignées dans l'inventaire ; leur consommation automatique par `make creer-vm` (au lieu des arguments en ligne de commande) est l'étape d'intégration suivante. Une valeur vide n'est pas écrite, pour garder l'inventaire propre. + ## Hôtes planifiés Les hôtes prévus mais non encore déployés sont placés dans `hotes_planifies`. diff --git a/docs/nomenclature.yml b/docs/nomenclature.yml new file mode 100644 index 0000000..024da34 --- /dev/null +++ b/docs/nomenclature.yml @@ -0,0 +1,42 @@ +--- +# Nomenclature Set-OPS : source unique pour hostnames, VMID, VLAN et adressage. +# +# Derivations (tout part du domaine de l'hote) : +# hostname = - ex. web-frontal-03 +# VMID = 9 . categorie . service . NN ex. 9 5 3 03 -> 95303 +# VLAN = categorie.vlan ex. 15 +# IP = .(service*10 + NN) ex. 10.1.15.33 +# passerelle / CIDR : voir la categorie +# +# Contrainte : NN va de 01 a 09 par domaine (l'octet hote = service*10 + NN +# reste dans le bloc du service). Au-dela, ouvrir un nouveau domaine ou service. + +supernet: "10.1.0.0/16" +cidr_hote: 24 + +reservations: + passerelle: 1 # .1 = passerelle de chaque /24 + reserve_min: 2 # .2 a .9 reserves (reseau, HA, futur) + reserve_max: 9 + +categories: + 1: { libelle: "Fondations / infra", vlan: 11, sous_reseau: "10.1.11.0/24", passerelle: "10.1.11.1" } + 2: { libelle: "Identite", vlan: 12, sous_reseau: "10.1.12.0/24", passerelle: "10.1.12.1" } + 3: { libelle: "Donnees", vlan: 13, sous_reseau: "10.1.13.0/24", passerelle: "10.1.13.1" } + 4: { libelle: "Observabilite", vlan: 14, sous_reseau: "10.1.14.0/24", passerelle: "10.1.14.1" } + 5: { libelle: "Applications", vlan: 15, sous_reseau: "10.1.15.0/24", passerelle: "10.1.15.1" } + 9: { libelle: "Modeles / essais", vlan: 99, sous_reseau: "10.1.99.0/24", passerelle: "10.1.99.1" } + +domaines: + infra-pki: { categorie: 1, service: 1 } + infra-edge: { categorie: 1, service: 2 } + infra-mail: { categorie: 1, service: 3 } + infra-dns: { categorie: 1, service: 4 } + idm: { categorie: 2, service: 1 } + data: { categorie: 3, service: 1 } + obs: { categorie: 4, service: 1 } + mon: { categorie: 4, service: 4 } + forge: { categorie: 5, service: 1 } + collab: { categorie: 5, service: 2 } + web-frontal: { categorie: 5, service: 3 } + web-dorsal: { categorie: 5, service: 4 } diff --git a/docs/procedure-template-debian13-proxmox.md b/docs/procedure-template-debian13-proxmox.md index 6dfe4fa..63e02e6 100644 --- a/docs/procedure-template-debian13-proxmox.md +++ b/docs/procedure-template-debian13-proxmox.md @@ -495,8 +495,8 @@ Exemple IP statique temporaire pour joindre la VM de modèle : ```bash qm set 9000 --ciuser ansible qm set 9000 --sshkeys ~/.ssh/id_ed25519.pub -qm set 9000 --ipconfig0 ip=192.168.11.150/24,gw=192.168.11.1 -qm set 9000 --nameserver 192.168.11.1 +qm set 9000 --ipconfig0 ip=10.1.99.99/24,gw=10.1.99.1 +qm set 9000 --nameserver 10.1.99.1 ``` Pour les clones, ces valeurs seront remplacées par l'identité réelle du serveur. @@ -761,11 +761,11 @@ Exemple : ```bash make creer-vm \ - HOTE=web-01 \ - VMID=101 \ + HOTE=web-frontal-01 \ + VMID=95301 \ VLAN=11 \ - ADRESSE_IP=192.168.11.101 \ - PASSERELLE=192.168.11.1 \ + ADRESSE_IP=10.1.15.31 \ + PASSERELLE=10.1.15.1 \ GROUPES="serveurs_debian serveurs_durcis" ``` @@ -774,13 +774,13 @@ Le VLAN est volontairement saisi au moment de créer chaque VM. Il ne fait pas p Après le premier démarrage, tester l'accès : ```bash -ssh ansible@192.168.11.101 +ssh ansible@10.1.15.31 ``` Ensuite appliquer la conformité Ansible : ```bash -make deployer HOTE=web-01 +make deployer HOTE=web-frontal-01 ``` --- diff --git a/docs/vm-lifecycle.md b/docs/vm-lifecycle.md index 6bee7ee..9ff3dad 100644 --- a/docs/vm-lifecycle.md +++ b/docs/vm-lifecycle.md @@ -114,8 +114,8 @@ serveurs_durcis -> playbooks/groupes/serveurs_durcis.yml L'appartenance aux groupes devient la déclaration d'intention : ```text -web-01 dans serveurs_debian -> applique le socle Debian -web-01 dans serveurs_durcis -> applique le durcissement commun +web-frontal-01 dans serveurs_debian -> applique le socle Debian +web-frontal-01 dans serveurs_durcis -> applique le durcissement commun ``` Un groupe sans playbook ne doit pas être assigné à une VM de production. @@ -145,8 +145,8 @@ L'accès SSH par mot de passe est désactivé dès le socle. L'activation de nft Pour une VM déjà clonée et démarrée : ```bash -make hote-ajouter HOTE=web-01 VMID=99101 ADRESSE_IP=192.168.12.101 GROUPES="serveurs_debian serveurs_durcis" -make deployer HOTE=web-01 +make hote-ajouter HOTE=web-frontal-01 VMID=95301 ADRESSE_IP=10.1.15.31 GROUPES="serveurs_debian serveurs_durcis" +make deployer HOTE=web-frontal-01 ``` La cible `deployer` lit les groupes de l'hôte, cherche les playbooks correspondants dans `playbooks/groupes/`, puis les applique dans l'ordre de l'inventaire. diff --git a/inventories/lab/hosts.yml b/inventories/lab/hosts.yml index eb8e21a..6b58664 100644 --- a/inventories/lab/hosts.yml +++ b/inventories/lab/hosts.yml @@ -3,7 +3,7 @@ all: modeles_vm: hosts: basiqueChezlepro: - ansible_host: 192.168.12.99 + ansible_host: 10.1.99.99 ansible_user: ansible ansible_become: true @@ -13,7 +13,10 @@ all: serveurs_durcis: hosts: {} - serveurs_web: + serveurs_web_frontaux: + hosts: {} + + serveurs_web_dorsaux: hosts: {} hotes_proxmox: diff --git a/inventories/production/group_vars/clients_ldap.yml b/inventories/production/group_vars/clients_ldap.yml new file mode 100644 index 0000000..ff5ecba --- /dev/null +++ b/inventories/production/group_vars/clients_ldap.yml @@ -0,0 +1,8 @@ +--- +# Conformite identite utilisateur cliente (hotes authentifiant via OpenLDAP). + +clients_ldap_uri: "ldap://idm-01.chezlepro.internal" +clients_ldap_base_dn: "dc=chezlepro,dc=internal" + +# Bind anonyme par defaut. Pour un compte de lecture dedie, definir ici +# clients_ldap_bind_dn et clients_ldap_bind_password ("{{ vault_ldap_sssd }}" via Vault). diff --git a/inventories/production/group_vars/clients_pki.yml b/inventories/production/group_vars/clients_pki.yml new file mode 100644 index 0000000..cd956ca --- /dev/null +++ b/inventories/production/group_vars/clients_pki.yml @@ -0,0 +1,11 @@ +--- +# Conformite PKI cliente (tout hote devant une identite machine). + +clients_pki_ca_url: "https://infra-pki-01.chezlepro.internal:8443" +clients_pki_provisioner: "admin@chezlepro.internal" + +# Secrets OBLIGATOIRES, a fournir via Ansible Vault avant tout deploiement reel : +# clients_pki_ca_fingerprint: "{{ vault_step_ca_fingerprint }}" +# clients_pki_provisioner_password: "{{ vault_step_ca_provisioner_password }}" +# Le provisioner password est PARTAGE avec serveurs_step_ca : le placer dans un +# vault partage (ex. group_vars/all/) lisible par les deux groupes. diff --git a/inventories/production/group_vars/clients_smtp.yml b/inventories/production/group_vars/clients_smtp.yml new file mode 100644 index 0000000..4906980 --- /dev/null +++ b/inventories/production/group_vars/clients_smtp.yml @@ -0,0 +1,5 @@ +--- +# Conformite relais mail client (hotes relayant vers serveurs_sendmail). + +clients_smtp_relais: "infra-mail-01.chezlepro.internal" +clients_smtp_port: 25 diff --git a/inventories/production/group_vars/serveurs_forgejo.yml b/inventories/production/group_vars/serveurs_forgejo.yml new file mode 100644 index 0000000..2d5ac12 --- /dev/null +++ b/inventories/production/group_vars/serveurs_forgejo.yml @@ -0,0 +1,9 @@ +--- +# Conformite Forgejo (forge-01, VLAN 15). + +serveurs_forgejo_hostname: "forge.chezlepro.internal" +serveurs_forgejo_db_host: "10.1.13.11" # data-01 + +# Secrets OBLIGATOIRES via Ansible Vault (vault partage pour vault_bd_forgejo) : +# serveurs_forgejo_secret_key, serveurs_forgejo_internal_token, +# serveurs_forgejo_admin_password, vault_bd_forgejo diff --git a/inventories/production/group_vars/serveurs_grafana.yml b/inventories/production/group_vars/serveurs_grafana.yml new file mode 100644 index 0000000..5273779 --- /dev/null +++ b/inventories/production/group_vars/serveurs_grafana.yml @@ -0,0 +1,8 @@ +--- +# Conformite Grafana (obs-01, VLAN 14). + +serveurs_grafana_hostname: "grafana.chezlepro.internal" + +# Secret OBLIGATOIRE, a fournir via Ansible Vault avant tout deploiement reel : +# serveurs_grafana_admin_password: "{{ vault_grafana_admin }}" +# Convertir en dossier group_vars/serveurs_grafana/ avec main.yml + vault.yml (chiffre). diff --git a/inventories/production/group_vars/serveurs_icinga.yml b/inventories/production/group_vars/serveurs_icinga.yml new file mode 100644 index 0000000..28bf721 --- /dev/null +++ b/inventories/production/group_vars/serveurs_icinga.yml @@ -0,0 +1,8 @@ +--- +# Conformite supervision Icinga (mon-01, VLAN 14). + +serveurs_icinga_db_host: "10.1.13.11" # data-01 (serveurs_postgresql) + +# Secret BD OBLIGATOIRE, a fournir via Ansible Vault (partage avec serveurs_postgresql) : +# vault_bd_icingadb: "..." +# A placer dans un vault partage (ex. group_vars/all/) lisible par les deux groupes. diff --git a/inventories/production/group_vars/serveurs_keycloak.yml b/inventories/production/group_vars/serveurs_keycloak.yml new file mode 100644 index 0000000..26788f1 --- /dev/null +++ b/inventories/production/group_vars/serveurs_keycloak.yml @@ -0,0 +1,12 @@ +--- +# Conformite Keycloak (idm-01, VLAN 12). + +serveurs_keycloak_hostname: "keycloak.chezlepro.internal" +serveurs_keycloak_db_host: "10.1.13.11" # data-01 (serveurs_postgresql) + +# Secrets OBLIGATOIRES, a fournir via Ansible Vault avant tout deploiement reel : +# - serveurs_keycloak_admin_password: "{{ vault_keycloak_admin }}" (admin bootstrap) +# - vault_bd_keycloak: "..." (mot de passe BD, partage avec serveurs_postgresql) +# Convertir en dossier group_vars/serveurs_keycloak/ avec main.yml (clair) + vault.yml (chiffre). +# Le secret BD (vault_bd_keycloak) doit etre lisible aussi par serveurs_postgresql +# (placer dans un vault partage, ex. group_vars/all/). diff --git a/inventories/production/group_vars/serveurs_nginx.yml b/inventories/production/group_vars/serveurs_nginx.yml new file mode 100644 index 0000000..de6c813 --- /dev/null +++ b/inventories/production/group_vars/serveurs_nginx.yml @@ -0,0 +1,9 @@ +--- +# Conformite nginx / edge (infra-edge-01, VLAN 11). + +# Sites de reverse proxy : a declarer ici quand un service web doit etre publie. +# L'amont pointe vers l'IP interne et le port du service (cf. nomenclature). +serveurs_nginx_sites: [] +# - nom: forge +# domaine: forge.chezlepro.internal +# amont: "http://10.1.15.11:3000" diff --git a/inventories/production/group_vars/serveurs_openldap.yml b/inventories/production/group_vars/serveurs_openldap.yml new file mode 100644 index 0000000..9cd0877 --- /dev/null +++ b/inventories/production/group_vars/serveurs_openldap.yml @@ -0,0 +1,11 @@ +--- +# Conformite annuaire OpenLDAP (idm-01, VLAN 12). + +serveurs_openldap_domaine: "chezlepro.internal" +serveurs_openldap_organisation: "Chezlepro Inc" + +# Secret OBLIGATOIRE, a fournir via Ansible Vault avant tout deploiement reel. +# Convertir en dossier group_vars/serveurs_openldap/ avec : +# main.yml : serveurs_openldap_admin_password: "{{ vault_openldap_admin }}" +# vault.yml : vault_openldap_admin: "..." (chiffre ansible-vault) +# Tant qu'il est absent, le role refuse de s'executer (assertion explicite). diff --git a/inventories/production/group_vars/serveurs_postgresql.yml b/inventories/production/group_vars/serveurs_postgresql.yml new file mode 100644 index 0000000..38933bf --- /dev/null +++ b/inventories/production/group_vars/serveurs_postgresql.yml @@ -0,0 +1,10 @@ +--- +# Conformite PostgreSQL (zone donnees, VLAN 13). + +serveurs_postgresql_reseaux_autorises: + - "10.1.0.0/16" + +# Comptes et bases applicatifs : a declarer ici quand un service en a besoin, +# avec les mots de passe references depuis Ansible Vault (jamais en clair). +serveurs_postgresql_comptes: [] +serveurs_postgresql_bases: [] diff --git a/inventories/production/group_vars/serveurs_redis.yml b/inventories/production/group_vars/serveurs_redis.yml new file mode 100644 index 0000000..06e9e9a --- /dev/null +++ b/inventories/production/group_vars/serveurs_redis.yml @@ -0,0 +1,8 @@ +--- +# Conformite Redis (data-01, VLAN 13). + +serveurs_redis_maxmemory: "256mb" + +# Secret OBLIGATOIRE, a fournir via Ansible Vault avant tout deploiement reel : +# serveurs_redis_password: "{{ vault_redis }}" +# Convertir en dossier group_vars/serveurs_redis/ avec main.yml + vault.yml (chiffre). diff --git a/inventories/production/group_vars/serveurs_sendmail.yml b/inventories/production/group_vars/serveurs_sendmail.yml new file mode 100644 index 0000000..7686945 --- /dev/null +++ b/inventories/production/group_vars/serveurs_sendmail.yml @@ -0,0 +1,11 @@ +--- +# Conformite relais SMTP (infra-mail-01, VLAN 11). + +serveurs_sendmail_domaine: "chezlepro.internal" +serveurs_sendmail_reseaux_autorises: + - "127.0.0.0/8" + - "[::1]/128" + - "10.1.0.0/16" + +# Smarthost amont : vide = remise directe. A renseigner si relais externe requis. +serveurs_sendmail_smarthost: "" diff --git a/inventories/production/group_vars/serveurs_step_ca.yml b/inventories/production/group_vars/serveurs_step_ca.yml new file mode 100644 index 0000000..bc03deb --- /dev/null +++ b/inventories/production/group_vars/serveurs_step_ca.yml @@ -0,0 +1,16 @@ +--- +# Conformite AC interne step-ca (infra-pki-01, VLAN 11). + +serveurs_step_ca_nom: "Chezlepro Internal CA" +serveurs_step_ca_adresse: ":8443" +serveurs_step_ca_acme: true + +# Secrets OBLIGATOIRES, a fournir via Ansible Vault avant tout deploiement reel. +# Convertir ce fichier en dossier group_vars/serveurs_step_ca/ avec : +# main.yml (en clair) : +# serveurs_step_ca_password: "{{ vault_step_ca_password }}" +# serveurs_step_ca_provisioner_password: "{{ vault_step_ca_provisioner_password }}" +# vault.yml (chiffre ansible-vault) : +# vault_step_ca_password: "..." +# vault_step_ca_provisioner_password: "..." +# Tant qu'ils sont absents, le role refuse de s'executer (assertion explicite). diff --git a/inventories/production/hosts.yml b/inventories/production/hosts.yml index c62ee5d..7f0bf9c 100644 --- a/inventories/production/hosts.yml +++ b/inventories/production/hosts.yml @@ -3,139 +3,251 @@ all: modeles_vm: hosts: {} hotes_actifs: - hosts: - web-01: - ansible_host: 192.168.12.101 - ansible_user: ansible - proxmox_vmid: 99101 - web-02: - ansible_host: 192.168.12.102 - ansible_user: ansible - proxmox_vmid: 99102 + hosts: {} hotes_planifies: hosts: - infra-pki-01: - proxmox_vmid: 91101 - infra-edge-01: - proxmox_vmid: 91201 - infra-mail-01: - proxmox_vmid: 91301 - infra-dns-01: - proxmox_vmid: 91401 - idm-01: - proxmox_vmid: 92101 - data-01: - proxmox_vmid: 93101 - obs-01: - proxmox_vmid: 94101 - mon-01: - proxmox_vmid: 94401 - forge-01: - proxmox_vmid: 95101 collab-01: + ansible_host: 10.1.15.21 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.15.1 + proxmox_vlan: 15 proxmox_vmid: 95201 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + data-01: + ansible_host: 10.1.13.11 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.13.1 + proxmox_vlan: 13 + proxmox_vmid: 93101 + proxmox_noeud: asgard + proxmox_stockage: TrueNAS + proxmox_disque_taille: 160G + proxmox_memoire: 2048 + proxmox_coeurs: 4 + forge-01: + ansible_host: 10.1.15.11 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.15.1 + proxmox_vlan: 15 + proxmox_vmid: 95101 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + idm-01: + ansible_host: 10.1.12.11 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.12.1 + proxmox_vlan: 12 + proxmox_vmid: 92101 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + infra-dns-01: + ansible_host: 10.1.11.41 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.11.1 + proxmox_vlan: 11 + proxmox_vmid: 91401 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + infra-edge-01: + ansible_host: 10.1.11.21 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.11.1 + proxmox_vlan: 11 + proxmox_vmid: 91201 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + infra-mail-01: + ansible_host: 10.1.11.31 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.11.1 + proxmox_vlan: 11 + proxmox_vmid: 91301 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + infra-pki-01: + ansible_host: 10.1.11.11 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.11.1 + proxmox_vlan: 11 + proxmox_vmid: 91101 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + mon-01: + ansible_host: 10.1.14.41 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.14.1 + proxmox_vlan: 14 + proxmox_vmid: 94401 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + obs-01: + ansible_host: 10.1.14.11 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.14.1 + proxmox_vlan: 14 + proxmox_vmid: 94101 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 4 + web-dorsal-01: + ansible_host: 10.1.15.41 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.15.1 + proxmox_vlan: 15 + proxmox_vmid: 95401 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + web-frontal-01: + ansible_host: 10.1.15.31 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.15.1 + proxmox_vlan: 15 + proxmox_vmid: 95301 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + web-frontal-02: + ansible_host: 10.1.15.32 + ansible_user: ansible + proxmox_cidr: 24 + proxmox_passerelle: 10.1.15.1 + proxmox_vlan: 15 + proxmox_vmid: 95302 + proxmox_stockage: TrueNAS + proxmox_disque_taille: 16G + proxmox_memoire: 2048 + proxmox_coeurs: 2 + clients_dns: + hosts: + collab-01: {} + web-frontal-01: {} + clients_journaux: + hosts: {} + clients_ldap: + hosts: {} + clients_metriques: + hosts: {} + clients_pki: + hosts: {} + clients_smtp: + hosts: {} + clients_supervision: + hosts: + web-frontal-01: {} + serveurs_collabora: + hosts: + collab-01: {} serveurs_debian: hosts: - web-01: - ansible_host: 192.168.12.101 - ansible_user: ansible - web-02: - ansible_host: 192.168.12.102 - ansible_user: ansible - infra-pki-01: {} + collab-01: {} + data-01: {} + forge-01: {} + idm-01: {} + infra-dns-01: {} infra-edge-01: {} infra-mail-01: {} - infra-dns-01: {} - idm-01: {} - data-01: {} - obs-01: {} + infra-pki-01: {} mon-01: {} - forge-01: {} - collab-01: {} + obs-01: {} + web-dorsal-01: {} + web-frontal-01: {} + web-frontal-02: {} serveurs_durcis: hosts: - web-01: - ansible_host: 192.168.12.101 - ansible_user: ansible - web-02: - ansible_host: 192.168.12.102 - ansible_user: ansible - infra-pki-01: {} + collab-01: {} + data-01: {} + forge-01: {} + idm-01: {} + infra-dns-01: {} infra-edge-01: {} infra-mail-01: {} - infra-dns-01: {} - idm-01: {} - data-01: {} - obs-01: {} + infra-pki-01: {} mon-01: {} - forge-01: {} - collab-01: {} - serveurs_web: + obs-01: {} + web-dorsal-01: {} + web-frontal-01: {} + web-frontal-02: {} + serveurs_forgejo: hosts: - web-01: - ansible_host: 192.168.12.101 - ansible_user: ansible - web-02: - ansible_host: 192.168.12.102 - ansible_user: ansible + forge-01: {} + serveurs_grafana: + hosts: + obs-01: {} + serveurs_icinga: + hosts: + mon-01: {} serveurs_keycloak: hosts: idm-01: {} + serveurs_loki: + hosts: + obs-01: {} + serveurs_nextcloud: + hosts: + collab-01: {} + serveurs_nginx: + hosts: + infra-edge-01: {} serveurs_openldap: hosts: idm-01: {} serveurs_postgresql: hosts: data-01: {} - serveurs_loki: + serveurs_powerdns: hosts: - obs-01: {} - serveurs_icinga: - hosts: - mon-01: {} + infra-dns-01: {} serveurs_prometheus: hosts: obs-01: {} - serveurs_grafana: + serveurs_redis: hosts: - obs-01: {} - serveurs_forgejo: - hosts: - forge-01: {} + data-01: {} serveurs_sendmail: hosts: infra-mail-01: {} serveurs_step_ca: hosts: infra-pki-01: {} - serveurs_powerdns: + serveurs_web_dorsaux: hosts: - infra-dns-01: {} - serveurs_redis: + web-dorsal-01: {} + serveurs_web_frontaux: hosts: - data-01: {} - serveurs_nginx: - hosts: - infra-edge-01: {} - serveurs_nextcloud: - hosts: - collab-01: {} - serveurs_collabora: - hosts: - collab-01: {} - clients_dns: - hosts: {} - clients_pki: - hosts: {} - clients_ldap: - hosts: {} - clients_supervision: - hosts: {} - clients_metriques: - hosts: {} - clients_journaux: - hosts: {} - clients_smtp: - hosts: {} + web-frontal-01: {} + web-frontal-02: {} hotes_proxmox: hosts: {} diff --git a/playbooks/groupes/clients_journaux.yml b/playbooks/groupes/clients_journaux.yml index 2ab057b..355415d 100644 --- a/playbooks/groupes/clients_journaux.yml +++ b/playbooks/groupes/clients_journaux.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que l'intégration cliente journaux reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe clients_journaux." + 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_journaux diff --git a/playbooks/groupes/clients_ldap.yml b/playbooks/groupes/clients_ldap.yml index 35e2602..ccfaf5d 100644 --- a/playbooks/groupes/clients_ldap.yml +++ b/playbooks/groupes/clients_ldap.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que l'intégration cliente LDAP reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe clients_ldap." + 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_ldap diff --git a/playbooks/groupes/clients_metriques.yml b/playbooks/groupes/clients_metriques.yml index ad2be79..6744b18 100644 --- a/playbooks/groupes/clients_metriques.yml +++ b/playbooks/groupes/clients_metriques.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que l'intégration cliente métriques reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe clients_metriques." + 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_metriques diff --git a/playbooks/groupes/clients_pki.yml b/playbooks/groupes/clients_pki.yml index d774f93..87396f4 100644 --- a/playbooks/groupes/clients_pki.yml +++ b/playbooks/groupes/clients_pki.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que l'intégration cliente PKI reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe clients_pki." + 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_pki diff --git a/playbooks/groupes/clients_smtp.yml b/playbooks/groupes/clients_smtp.yml index dc9c449..f30fa92 100644 --- a/playbooks/groupes/clients_smtp.yml +++ b/playbooks/groupes/clients_smtp.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que l'intégration cliente SMTP reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe clients_smtp." + 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_smtp diff --git a/playbooks/groupes/serveurs_forgejo.yml b/playbooks/groupes/serveurs_forgejo.yml index 0b25ac8..6eeaae4 100644 --- a/playbooks/groupes/serveurs_forgejo.yml +++ b/playbooks/groupes/serveurs_forgejo.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Forgejo reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_forgejo." + 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_forgejo diff --git a/playbooks/groupes/serveurs_grafana.yml b/playbooks/groupes/serveurs_grafana.yml index efc16e6..f143620 100644 --- a/playbooks/groupes/serveurs_grafana.yml +++ b/playbooks/groupes/serveurs_grafana.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Grafana reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_grafana." + 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_grafana diff --git a/playbooks/groupes/serveurs_icinga.yml b/playbooks/groupes/serveurs_icinga.yml index 46ba9c5..6143511 100644 --- a/playbooks/groupes/serveurs_icinga.yml +++ b/playbooks/groupes/serveurs_icinga.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que la plateforme Icinga reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_icinga." + 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_icinga diff --git a/playbooks/groupes/serveurs_keycloak.yml b/playbooks/groupes/serveurs_keycloak.yml index 3b253ea..1333719 100644 --- a/playbooks/groupes/serveurs_keycloak.yml +++ b/playbooks/groupes/serveurs_keycloak.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Keycloak reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_keycloak." + 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_keycloak diff --git a/playbooks/groupes/serveurs_loki.yml b/playbooks/groupes/serveurs_loki.yml index 3ccb1cf..9e488e2 100644 --- a/playbooks/groupes/serveurs_loki.yml +++ b/playbooks/groupes/serveurs_loki.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Loki reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_loki." + 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_loki diff --git a/playbooks/groupes/serveurs_nginx.yml b/playbooks/groupes/serveurs_nginx.yml index 38d94bc..6937c5b 100644 --- a/playbooks/groupes/serveurs_nginx.yml +++ b/playbooks/groupes/serveurs_nginx.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service NGINX reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_nginx." + 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_nginx diff --git a/playbooks/groupes/serveurs_openldap.yml b/playbooks/groupes/serveurs_openldap.yml index aa0abfa..123ab82 100644 --- a/playbooks/groupes/serveurs_openldap.yml +++ b/playbooks/groupes/serveurs_openldap.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service OpenLDAP reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_openldap." + 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_openldap diff --git a/playbooks/groupes/serveurs_postgresql.yml b/playbooks/groupes/serveurs_postgresql.yml index b0dc0b5..4cd3e34 100644 --- a/playbooks/groupes/serveurs_postgresql.yml +++ b/playbooks/groupes/serveurs_postgresql.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service PostgreSQL reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_postgresql." + 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_postgresql diff --git a/playbooks/groupes/serveurs_prometheus.yml b/playbooks/groupes/serveurs_prometheus.yml index 6b9c5c2..448d16d 100644 --- a/playbooks/groupes/serveurs_prometheus.yml +++ b/playbooks/groupes/serveurs_prometheus.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Prometheus reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_prometheus." + 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_prometheus diff --git a/playbooks/groupes/serveurs_redis.yml b/playbooks/groupes/serveurs_redis.yml index ce1a250..bebcc8f 100644 --- a/playbooks/groupes/serveurs_redis.yml +++ b/playbooks/groupes/serveurs_redis.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Redis reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_redis." + 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_redis diff --git a/playbooks/groupes/serveurs_sendmail.yml b/playbooks/groupes/serveurs_sendmail.yml index ab345d9..4315e11 100644 --- a/playbooks/groupes/serveurs_sendmail.yml +++ b/playbooks/groupes/serveurs_sendmail.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service Sendmail reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_sendmail." + 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_sendmail diff --git a/playbooks/groupes/serveurs_step_ca.yml b/playbooks/groupes/serveurs_step_ca.yml index 8c5af2e..539119e 100644 --- a/playbooks/groupes/serveurs_step_ca.yml +++ b/playbooks/groupes/serveurs_step_ca.yml @@ -4,7 +4,12 @@ become: true gather_facts: true - tasks: - - name: Indiquer que le service step-ca reste à définir - ansible.builtin.debug: - msg: "Aucun rôle n'est encore associé au groupe serveurs_step_ca." + 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_step_ca diff --git a/playbooks/groupes/serveurs_web.yml b/playbooks/groupes/serveurs_web_dorsaux.yml similarity index 52% rename from playbooks/groupes/serveurs_web.yml rename to playbooks/groupes/serveurs_web_dorsaux.yml index 7355f40..e93423b 100644 --- a/playbooks/groupes/serveurs_web.yml +++ b/playbooks/groupes/serveurs_web_dorsaux.yml @@ -1,6 +1,6 @@ --- -- name: Appliquer le groupe serveurs_web - hosts: serveurs_web +- name: Appliquer le groupe serveurs_web_dorsaux + hosts: serveurs_web_dorsaux become: true gather_facts: true @@ -12,6 +12,6 @@ fail_msg: "Ce playbook est prévu pour Debian." tasks: - - name: Indiquer que le rôle web reste à définir + - name: Indiquer que le rôle dorsal web reste à définir ansible.builtin.debug: - msg: "Aucun rôle web n'est encore associé au groupe serveurs_web." + msg: "Aucun rôle n'est encore associé au groupe serveurs_web_dorsaux (couche application web : API, traitement)." diff --git a/playbooks/groupes/serveurs_web_frontaux.yml b/playbooks/groupes/serveurs_web_frontaux.yml new file mode 100644 index 0000000..006b9af --- /dev/null +++ b/playbooks/groupes/serveurs_web_frontaux.yml @@ -0,0 +1,17 @@ +--- +- name: Appliquer le groupe serveurs_web_frontaux + hosts: serveurs_web_frontaux + 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." + + tasks: + - name: Indiquer que le rôle frontal web reste à définir + ansible.builtin.debug: + msg: "Aucun rôle n'est encore associé au groupe serveurs_web_frontaux (couche présentation web, derrière serveurs_nginx)." diff --git a/playbooks/proxmox/README.md b/playbooks/proxmox/README.md index cdd4257..07a3c31 100644 --- a/playbooks/proxmox/README.md +++ b/playbooks/proxmox/README.md @@ -54,11 +54,11 @@ Créer un clone Debian depuis le modèle : ```bash make creer-vm \ - HOTE=web-01 \ - VMID=101 \ - VLAN=12 \ - ADRESSE_IP=192.168.12.101 \ - PASSERELLE=192.168.12.1 \ + HOTE=web-frontal-01 \ + VMID=95301 \ + VLAN=15 \ + ADRESSE_IP=10.1.15.31 \ + PASSERELLE=10.1.15.1 \ GROUPES="serveurs_debian serveurs_durcis" ``` @@ -67,7 +67,7 @@ make creer-vm \ Le disque du clone hérite de la taille du modèle. Pour l'agrandir à la création, fournir explicitement une taille supérieure à celle du modèle : ```bash -make creer-vm HOTE=web-01 VMID=101 VLAN=12 ADRESSE_IP=192.168.12.101 PASSERELLE=192.168.12.1 TAILLE_DISQUE=64G GROUPES="serveurs_debian serveurs_durcis" +make creer-vm HOTE=web-frontal-01 VMID=95301 VLAN=15 ADRESSE_IP=10.1.15.31 PASSERELLE=10.1.15.1 TAILLE_DISQUE=64G GROUPES="serveurs_debian serveurs_durcis" ``` Proxmox ne permet pas de réduire un disque existant. @@ -75,5 +75,5 @@ Proxmox ne permet pas de réduire un disque existant. `creer-vm` clone la VM, applique les paramètres Cloud-Init et ajoute l'hôte à l'inventaire. La conformité Ansible reste séparée : ```bash -make deployer HOTE=web-01 +make deployer HOTE=web-frontal-01 ``` diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..c097f1a --- /dev/null +++ b/requirements.yml @@ -0,0 +1,6 @@ +--- +# Collections Ansible requises par Set-OPS. +# Installer : ansible-galaxy collection install -r requirements.yml +collections: + - name: community.postgresql + - name: community.general diff --git a/roles/clients_journaux/README.md b/roles/clients_journaux/README.md new file mode 100644 index 0000000..7285ec6 --- /dev/null +++ b/roles/clients_journaux/README.md @@ -0,0 +1,27 @@ +# clients_journaux + +Intégration cliente **journaux** : expédie le journal système (journald) de la VM +vers `serveurs_loki`, via **Grafana Alloy**. + +## Rôle +- Ajoute le **dépôt apt Grafana** et installe `alloy`. +- Ajoute l'utilisateur `alloy` au groupe `systemd-journal` (lecture du journal). +- Déploie `/etc/alloy/config.alloy` : `loki.source.journal` → `loki.write` vers Loki. + +## Boucle +VM dans `clients_journaux` → Alloy lit journald → pousse vers Loki (obs-01) → +visible dans Grafana (datasource Loki). + +## Variables +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `clients_journaux_loki_url` | `http://10.1.14.11:3100/loki/api/v1/push` | Endpoint Loki (obs-01) | + +## Notes / limites +- La **syntaxe de configuration Alloy évolue** entre versions ; la config fournie + cible un Alloy récent (`loki.source.journal`, `loki.write`). Ajustement mineur + possible selon la version installée. +- Étiquettes par défaut : `job=systemd-journal`, `host=`. + +## Prérequis +- Dépendance `clients_journaux requiert serveurs_loki actif` (déjà dans `docs/dependances-groupes.yml`). diff --git a/roles/clients_journaux/defaults/main.yml b/roles/clients_journaux/defaults/main.yml new file mode 100644 index 0000000..f1bcbe8 --- /dev/null +++ b/roles/clients_journaux/defaults/main.yml @@ -0,0 +1,15 @@ +--- +clients_journaux_paquets: + - alloy + +clients_journaux_service: "alloy" +clients_journaux_utilisateur: "alloy" +clients_journaux_config: "/etc/alloy/config.alloy" + +# Point de collecte Loki (obs-01, serveurs_loki, VLAN 14). +clients_journaux_loki_url: "http://10.1.14.11:3100/loki/api/v1/push" + +# Depot apt officiel Grafana (partage avec serveurs_loki / serveurs_grafana). +clients_journaux_depot_cle_url: "https://apt.grafana.com/gpg-full.key" +clients_journaux_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc" +clients_journaux_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" diff --git a/roles/clients_journaux/handlers/main.yml b/roles/clients_journaux/handlers/main.yml new file mode 100644 index 0000000..ee72910 --- /dev/null +++ b/roles/clients_journaux/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Redemarrer alloy + ansible.builtin.systemd: + name: "{{ clients_journaux_service }}" + state: restarted + listen: Redemarrer alloy diff --git a/roles/clients_journaux/tasks/main.yml b/roles/clients_journaux/tasks/main.yml new file mode 100644 index 0000000..bc453cd --- /dev/null +++ b/roles/clients_journaux/tasks/main.yml @@ -0,0 +1,50 @@ +--- +- name: Assurer le repertoire des trousseaux apt + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + +- name: Telecharger la cle de signature Grafana + ansible.builtin.get_url: + url: "{{ clients_journaux_depot_cle_url }}" + dest: "{{ clients_journaux_depot_cle_fichier }}" + owner: root + group: root + mode: "0644" + +- name: Ajouter le depot apt Grafana + ansible.builtin.apt_repository: + repo: "{{ clients_journaux_depot_source }}" + filename: grafana + state: present + +- name: Installer Grafana Alloy + ansible.builtin.apt: + name: "{{ clients_journaux_paquets }}" + state: present + update_cache: true + +- name: Autoriser Alloy a lire le journal systeme + ansible.builtin.user: + name: "{{ clients_journaux_utilisateur }}" + groups: systemd-journal + append: true + notify: Redemarrer alloy + +- name: Deployer la configuration Alloy + ansible.builtin.template: + src: config.alloy.j2 + dest: "{{ clients_journaux_config }}" + owner: root + group: "{{ clients_journaux_utilisateur }}" + mode: "0640" + notify: Redemarrer alloy + +- name: Activer et demarrer Alloy + ansible.builtin.systemd: + name: "{{ clients_journaux_service }}" + enabled: true + state: started diff --git a/roles/clients_journaux/templates/config.alloy.j2 b/roles/clients_journaux/templates/config.alloy.j2 new file mode 100644 index 0000000..17c22ab --- /dev/null +++ b/roles/clients_journaux/templates/config.alloy.j2 @@ -0,0 +1,16 @@ +// Gere par Set-OPS (role clients_journaux). Ne pas editer a la main. + +loki.write "loki" { + endpoint { + url = "{{ clients_journaux_loki_url }}" + } +} + +loki.source.journal "journal" { + forward_to = [loki.write.loki.receiver] + max_age = "12h" + labels = { + job = "systemd-journal", + host = "{{ inventory_hostname }}", + } +} diff --git a/roles/clients_ldap/README.md b/roles/clients_ldap/README.md new file mode 100644 index 0000000..9f30b4a --- /dev/null +++ b/roles/clients_ldap/README.md @@ -0,0 +1,35 @@ +# clients_ldap + +Intégration cliente **identité utilisateur** : l'hôte authentifie les utilisateurs +via l'annuaire central `serveurs_openldap`, grâce à **SSSD** (NSS + PAM). + +## Rôle +- Installe `sssd`, `sssd-ldap`, `libnss-sss`, `libpam-sss`. +- Déploie `/etc/sssd/sssd.conf` (`0600`) pointant vers OpenLDAP (`id_provider`/`auth_provider = ldap`). +- Active `sss` dans `/etc/nsswitch.conf` (passwd, group, shadow). +- Active la **création automatique du répertoire personnel** (`pam-auth-update --enable mkhomedir`). +- `libpam-sss` active automatiquement le module PAM `sss`. + +## Effet +Après ce rôle, les comptes de l'annuaire Chezlepro peuvent se connecter à l'hôte +(SSH, console) et leur home est créé à la première connexion. Couplé à `clients_pki` +(identité machine), l'écosystème a une authentification **machines + utilisateurs** centralisée. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `clients_ldap_uri` | `ldap://idm-01.chezlepro.internal` | URI de l'annuaire | +| `clients_ldap_base_dn` | `dc=chezlepro,dc=internal` | Base de recherche | +| `clients_ldap_bind_dn` | `""` (anonyme) | Compte de lecture (optionnel, mdp via Vault) | +| `clients_ldap_tls_reqcert` | `never` | Validation TLS (→ `demand` avec LDAPS) | + +## Notes / limites +- **Bind anonyme** par défaut : suppose une lecture publique de l'annuaire. Pour un + bind dédié, définir `clients_ldap_bind_dn` + `clients_ldap_bind_password` (Vault) et + créer le compte de service dans l'annuaire. +- **LDAP en clair** sur le réseau interne pour l'instant ; basculer en **LDAPS** quand + OpenLDAP exposera un certificat (step_ca), avec `clients_ldap_tls_reqcert: demand`. +- Les entrées utilisateurs/groupes (POSIX) doivent exister dans l'annuaire (gestion d'identité). + +## Prérequis +- Dépendance `clients_ldap requiert serveurs_openldap actif` (déjà dans `docs/dependances-groupes.yml`). diff --git a/roles/clients_ldap/defaults/main.yml b/roles/clients_ldap/defaults/main.yml new file mode 100644 index 0000000..de608cd --- /dev/null +++ b/roles/clients_ldap/defaults/main.yml @@ -0,0 +1,24 @@ +--- +clients_ldap_paquets: + - sssd + - sssd-ldap + - libnss-sss + - libpam-sss + - ldap-utils + +clients_ldap_service: "sssd" + +clients_ldap_domaine: "chezlepro" +clients_ldap_uri: "ldap://idm-01.chezlepro.internal" +clients_ldap_base_dn: "dc=chezlepro,dc=internal" + +# Bind de lecture. Vide => bind anonyme (lecture publique de l'annuaire). +clients_ldap_bind_dn: "" +clients_ldap_bind_password: "" # {{ vault_ldap_sssd }} si un bind DN est defini + +# TLS : "never" tant que OpenLDAP n'expose pas LDAPS ; passer a "demand" +# avec LDAPS + confiance step_ca (clients_pki). +clients_ldap_tls_reqcert: "never" + +# Creation automatique du repertoire personnel a la premiere connexion. +clients_ldap_mkhomedir: true diff --git a/roles/clients_ldap/handlers/main.yml b/roles/clients_ldap/handlers/main.yml new file mode 100644 index 0000000..fdb3acd --- /dev/null +++ b/roles/clients_ldap/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Redemarrer sssd + ansible.builtin.systemd: + name: "{{ clients_ldap_service }}" + state: restarted + listen: Redemarrer sssd diff --git a/roles/clients_ldap/tasks/main.yml b/roles/clients_ldap/tasks/main.yml new file mode 100644 index 0000000..c859e90 --- /dev/null +++ b/roles/clients_ldap/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Installer SSSD et les modules LDAP + ansible.builtin.apt: + name: "{{ clients_ldap_paquets }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Deployer la configuration SSSD + ansible.builtin.template: + src: sssd.conf.j2 + dest: /etc/sssd/sssd.conf + owner: root + group: root + mode: "0600" + no_log: true + notify: Redemarrer sssd + +- name: Activer SSS dans nsswitch + ansible.builtin.lineinfile: + path: /etc/nsswitch.conf + regexp: '^({{ item }}:\s+)(?!.*\bsss\b)(.*)$' + line: '\1\2 sss' + backrefs: true + loop: + - passwd + - group + - shadow + +- name: Activer la creation automatique des repertoires personnels + ansible.builtin.command: + cmd: pam-auth-update --enable mkhomedir + changed_when: false + when: clients_ldap_mkhomedir | bool + +- name: Activer et demarrer SSSD + ansible.builtin.systemd: + name: "{{ clients_ldap_service }}" + enabled: true + state: started diff --git a/roles/clients_ldap/templates/sssd.conf.j2 b/roles/clients_ldap/templates/sssd.conf.j2 new file mode 100644 index 0000000..6446600 --- /dev/null +++ b/roles/clients_ldap/templates/sssd.conf.j2 @@ -0,0 +1,18 @@ +# Gere par Set-OPS (role clients_ldap). Ne pas editer a la main. +[sssd] +config_file_version = 2 +services = nss, pam +domains = {{ clients_ldap_domaine }} + +[domain/{{ clients_ldap_domaine }}] +id_provider = ldap +auth_provider = ldap +ldap_uri = {{ clients_ldap_uri }} +ldap_search_base = {{ clients_ldap_base_dn }} +{% if clients_ldap_bind_dn | length > 0 %} +ldap_default_bind_dn = {{ clients_ldap_bind_dn }} +ldap_default_authtok = {{ clients_ldap_bind_password }} +{% endif %} +ldap_tls_reqcert = {{ clients_ldap_tls_reqcert }} +cache_credentials = true +enumerate = false diff --git a/roles/clients_metriques/README.md b/roles/clients_metriques/README.md new file mode 100644 index 0000000..73e11ff --- /dev/null +++ b/roles/clients_metriques/README.md @@ -0,0 +1,26 @@ +# clients_metriques + +Intégration cliente **métriques** : installe `node_exporter` sur la VM pour qu'elle +soit collectée par `serveurs_prometheus`. + +## Rôle +- Installe `prometheus-node-exporter` (paquet Debian). +- Configure l'adresse d'écoute (`:9100` par défaut). +- Active et démarre le service. + +## Boucle complète +1. On ajoute une VM au groupe `clients_metriques` et on l'active. +2. Ce rôle y installe `node_exporter` (`:9100`). +3. `serveurs_prometheus` **dérive automatiquement** la cible depuis l'inventaire + (hôtes actifs de `clients_metriques`) et la scrape. + +Aucun edit manuel côté Prometheus. + +## Variables +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `clients_metriques_adresse_ecoute` | `:9100` | `--web.listen-address` | + +## Prérequis +- Dépendance `clients_metriques requiert serveurs_prometheus actif` (déjà dans `docs/dependances-groupes.yml`). +- Accès du serveur Prometheus au port `9100` (segmentation réseau / nftables). diff --git a/roles/clients_metriques/defaults/main.yml b/roles/clients_metriques/defaults/main.yml new file mode 100644 index 0000000..9d59154 --- /dev/null +++ b/roles/clients_metriques/defaults/main.yml @@ -0,0 +1,9 @@ +--- +clients_metriques_paquets: + - prometheus-node-exporter + +clients_metriques_service: "prometheus-node-exporter" + +# Adresse d'ecoute de node_exporter. Par defaut toutes les interfaces ; la +# segmentation reseau (VLAN / nftables) restreint l'acces au serveur Prometheus. +clients_metriques_adresse_ecoute: ":9100" diff --git a/roles/clients_metriques/handlers/main.yml b/roles/clients_metriques/handlers/main.yml new file mode 100644 index 0000000..4ef887f --- /dev/null +++ b/roles/clients_metriques/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Redemarrer node_exporter + ansible.builtin.systemd: + name: "{{ clients_metriques_service }}" + state: restarted + listen: Redemarrer node_exporter diff --git a/roles/clients_metriques/tasks/main.yml b/roles/clients_metriques/tasks/main.yml new file mode 100644 index 0000000..8ed79d3 --- /dev/null +++ b/roles/clients_metriques/tasks/main.yml @@ -0,0 +1,22 @@ +--- +- name: Installer node_exporter + ansible.builtin.apt: + name: "{{ clients_metriques_paquets }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Definir les arguments de node_exporter (adresse d'ecoute) + ansible.builtin.template: + src: default-node-exporter.j2 + dest: /etc/default/prometheus-node-exporter + owner: root + group: root + mode: "0644" + notify: Redemarrer node_exporter + +- name: Activer et demarrer node_exporter + ansible.builtin.systemd: + name: "{{ clients_metriques_service }}" + enabled: true + state: started diff --git a/roles/clients_metriques/templates/default-node-exporter.j2 b/roles/clients_metriques/templates/default-node-exporter.j2 new file mode 100644 index 0000000..95fbe7f --- /dev/null +++ b/roles/clients_metriques/templates/default-node-exporter.j2 @@ -0,0 +1,2 @@ +# Gere par Set-OPS (role clients_metriques). +ARGS="--web.listen-address={{ clients_metriques_adresse_ecoute }}" diff --git a/roles/clients_pki/README.md b/roles/clients_pki/README.md new file mode 100644 index 0000000..025ea75 --- /dev/null +++ b/roles/clients_pki/README.md @@ -0,0 +1,43 @@ +# clients_pki + +Intégration cliente **PKI / identité machine** : chaque hôte fait confiance à l'AC +interne (`serveurs_step_ca`), obtient **son propre certificat** et le renouvelle +automatiquement. + +## Rôle +- Installe `step-cli` (dépôt apt Smallstep). +- **Confiance** : `step ca bootstrap --install` → installe la racine de l'AC dans le + magasin de confiance système (l'hôte fait confiance au TLS interne réel). +- **Certificat d'hôte** : `step ca certificate ` via le provisioner. +- **Renouvellement automatique** : unités systemd officielles `cert-renewer@.{service,timer}` + (vérifie/renouvelle toutes les 15 min, recharge le service consommateur s'il existe). + +## « Chaque hôte authentifiable » +Après ce rôle, l'hôte possède une **identité machine** vérifiable : un certificat émis +par l'AC Chezlepro, renouvelé sans intervention. Les services peuvent l'utiliser pour +du TLS / mTLS interne. + +## Secrets requis (Vault) +```yaml +clients_pki_ca_fingerprint: "{{ vault_step_ca_fingerprint }}" # empreinte racine de l'AC +clients_pki_provisioner_password: "{{ vault_step_ca_provisioner_password }}" # partage avec serveurs_step_ca +``` +L'empreinte racine s'obtient sur l'AC (`step certificate fingerprint `). + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `clients_pki_ca_url` | `https://infra-pki-01.chezlepro.internal:8443` | URL de l'AC | +| `clients_pki_provisioner` | `admin@chezlepro.internal` | Provisioner émetteur | +| `clients_pki_nom_cert` | FQDN de l'hôte | Nom du certificat | + +## Notes / sécurité +- Émission via le provisioner **JWK** (mot de passe partagé) : tout hôte avec ce secret + peut émettre des certificats. Acceptable en interne ; pour durcir, basculer vers ACME + ou des jetons à usage unique par hôte (phase ultérieure). +- Pour qu'un service (nginx, keycloak…) recharge automatiquement après renouvellement, + nommer son certificat d'après le service (instance `cert-renewer@`). + +## Prérequis +- Dépendance `clients_pki requiert serveurs_step_ca actif` (déjà dans `docs/dependances-groupes.yml`). +- Réseau vers l'AC (`:8443`). diff --git a/roles/clients_pki/defaults/main.yml b/roles/clients_pki/defaults/main.yml new file mode 100644 index 0000000..a370a88 --- /dev/null +++ b/roles/clients_pki/defaults/main.yml @@ -0,0 +1,24 @@ +--- +clients_pki_paquets: + - step-cli + +clients_pki_steppath: "/etc/step" + +# AC interne (serveurs_step_ca / infra-pki-01). +clients_pki_ca_url: "https://infra-pki-01.chezlepro.internal:8443" +clients_pki_provisioner: "admin@chezlepro.internal" + +# Identite de l'hote. +clients_pki_nom_cert: "{{ ansible_fqdn | default(ansible_hostname) }}" +clients_pki_cert: "{{ clients_pki_steppath }}/certs/{{ clients_pki_nom_cert }}.crt" +clients_pki_cle: "{{ clients_pki_steppath }}/certs/{{ clients_pki_nom_cert }}.key" + +# Secrets OBLIGATOIRES (Ansible Vault). +clients_pki_ca_fingerprint: "" # {{ vault_step_ca_fingerprint }} (empreinte racine de l'AC) +clients_pki_provisioner_password: "" # {{ vault_step_ca_provisioner_password }} (partage avec serveurs_step_ca) + +# Depot apt officiel Smallstep (partage avec serveurs_step_ca). +clients_pki_depot_cle_url: "https://packages.smallstep.com/keys/apt/repo-signing-key.gpg" +clients_pki_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc" +clients_pki_depot_uri: "https://packages.smallstep.com/stable/debian" +clients_pki_depot_suite: "debs" diff --git a/roles/clients_pki/handlers/main.yml b/roles/clients_pki/handlers/main.yml new file mode 100644 index 0000000..aa64dd4 --- /dev/null +++ b/roles/clients_pki/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Recharger systemd + ansible.builtin.systemd: + daemon_reload: true + listen: Recharger systemd diff --git a/roles/clients_pki/tasks/main.yml b/roles/clients_pki/tasks/main.yml new file mode 100644 index 0000000..0a9169d --- /dev/null +++ b/roles/clients_pki/tasks/main.yml @@ -0,0 +1,104 @@ +--- +- name: Exiger les secrets PKI (Vault) + ansible.builtin.assert: + that: + - clients_pki_ca_fingerprint | length > 0 + - clients_pki_provisioner_password | length > 0 + fail_msg: >- + clients_pki_ca_fingerprint et clients_pki_provisioner_password + sont requis (a fournir via Ansible Vault). + +- name: Assurer le repertoire des trousseaux apt + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + +- name: Telecharger la cle de signature Smallstep + ansible.builtin.get_url: + url: "{{ clients_pki_depot_cle_url }}" + dest: "{{ clients_pki_depot_cle_fichier }}" + owner: root + group: root + mode: "0644" + +- name: Ajouter le depot apt Smallstep + ansible.builtin.template: + src: smallstep.sources.j2 + dest: /etc/apt/sources.list.d/smallstep.sources + owner: root + group: root + mode: "0644" + +- name: Installer step-cli + ansible.builtin.apt: + name: "{{ clients_pki_paquets }}" + state: present + update_cache: true + +- name: Creer le repertoire STEPPATH des certificats + ansible.builtin.file: + path: "{{ clients_pki_steppath }}/certs" + state: directory + owner: root + group: root + mode: "0755" + +- name: Deployer le mot de passe du provisioner + ansible.builtin.copy: + content: "{{ clients_pki_provisioner_password }}" + dest: "{{ clients_pki_steppath }}/provisioner.pass" + owner: root + group: root + mode: "0600" + no_log: true + +- name: Etablir la confiance dans l'AC interne (bootstrap + installation racine) + ansible.builtin.command: + cmd: >- + step ca bootstrap + --ca-url {{ clients_pki_ca_url }} + --fingerprint {{ clients_pki_ca_fingerprint }} + --install --force + creates: "{{ clients_pki_steppath }}/certs/root_ca.crt" + environment: + STEPPATH: "{{ clients_pki_steppath }}" + +- name: Obtenir le certificat d'hote + ansible.builtin.command: + cmd: >- + step ca certificate {{ clients_pki_nom_cert }} + {{ clients_pki_cert }} {{ clients_pki_cle }} + --provisioner {{ clients_pki_provisioner }} + --provisioner-password-file {{ clients_pki_steppath }}/provisioner.pass + --force + creates: "{{ clients_pki_cert }}" + environment: + STEPPATH: "{{ clients_pki_steppath }}" + +- name: Deployer l'unite systemd de renouvellement + ansible.builtin.template: + src: cert-renewer@.service.j2 + dest: /etc/systemd/system/cert-renewer@.service + owner: root + group: root + mode: "0644" + notify: Recharger systemd + +- name: Deployer le minuteur de renouvellement + ansible.builtin.template: + src: cert-renewer@.timer.j2 + dest: /etc/systemd/system/cert-renewer@.timer + owner: root + group: root + mode: "0644" + notify: Recharger systemd + +- name: Activer le renouvellement automatique du certificat d'hote + ansible.builtin.systemd: + name: "cert-renewer@{{ clients_pki_nom_cert }}.timer" + enabled: true + state: started + daemon_reload: true diff --git a/roles/clients_pki/templates/cert-renewer@.service.j2 b/roles/clients_pki/templates/cert-renewer@.service.j2 new file mode 100644 index 0000000..6d72156 --- /dev/null +++ b/roles/clients_pki/templates/cert-renewer@.service.j2 @@ -0,0 +1,20 @@ +# Gere par Set-OPS (role clients_pki). Renouvellement de certificat (Smallstep). +[Unit] +Description=Certificate renewer for %I +After=network-online.target +Wants=network-online.target +Documentation=https://smallstep.com/docs/step-ca/renewal +StartLimitIntervalSec=0 + +[Service] +Type=oneshot +User=root +Environment=STEPPATH={{ clients_pki_steppath }} +Environment=CERT_LOCATION={{ clients_pki_steppath }}/certs/%i.crt +Environment=KEY_LOCATION={{ clients_pki_steppath }}/certs/%i.key +ExecCondition=/usr/bin/step certificate needs-renewal ${CERT_LOCATION} +ExecStart=/usr/bin/step ca renew --force ${CERT_LOCATION} ${KEY_LOCATION} +ExecStartPost=/usr/bin/env sh -c "! systemctl --quiet is-active %i.service || systemctl try-reload-or-restart %i" + +[Install] +WantedBy=multi-user.target diff --git a/roles/clients_pki/templates/cert-renewer@.timer.j2 b/roles/clients_pki/templates/cert-renewer@.timer.j2 new file mode 100644 index 0000000..64ef598 --- /dev/null +++ b/roles/clients_pki/templates/cert-renewer@.timer.j2 @@ -0,0 +1,12 @@ +# Gere par Set-OPS (role clients_pki). +[Unit] +Description=Timer for certificate renewal of %I +Documentation=https://smallstep.com/docs/step-ca/renewal + +[Timer] +Persistent=true +OnCalendar=*:1/15 +RandomizedDelaySec=5m + +[Install] +WantedBy=timers.target diff --git a/roles/clients_pki/templates/smallstep.sources.j2 b/roles/clients_pki/templates/smallstep.sources.j2 new file mode 100644 index 0000000..4ee103b --- /dev/null +++ b/roles/clients_pki/templates/smallstep.sources.j2 @@ -0,0 +1,5 @@ +Types: deb +URIs: {{ clients_pki_depot_uri }} +Suites: {{ clients_pki_depot_suite }} +Components: main +Signed-By: {{ clients_pki_depot_cle_fichier }} diff --git a/roles/clients_smtp/README.md b/roles/clients_smtp/README.md new file mode 100644 index 0000000..b7b262f --- /dev/null +++ b/roles/clients_smtp/README.md @@ -0,0 +1,29 @@ +# clients_smtp + +Intégration cliente **relais mail** : l'hôte relaie son courrier sortant (notifications, +cron, alertes) vers `serveurs_sendmail`, via **msmtp**. + +## Rôle +- Installe `msmtp` et `msmtp-mta` (`msmtp-mta` fournit `/usr/sbin/sendmail`, donc le + courrier système — cron, etc. — passe par le relais). +- Déploie `/etc/msmtprc` pointant vers le relais central (port 25, sans auth : le relais + fait confiance au réseau interne). + +## Effet +Tout courrier émis localement (`mail`, cron, scripts) est relayé vers `serveurs_sendmail` +(infra-mail-01), qui assure la remise. Léger, sans daemon. + +## Variables +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `clients_smtp_relais` | `infra-mail-01.chezlepro.internal` | MTA relais | +| `clients_smtp_port` | `25` | Port du relais | +| `clients_smtp_from` | `@chezlepro.internal` | Expéditeur par défaut | +| `clients_smtp_tls` | `false` | TLS (→ `true` avec certificats step_ca) | + +## Notes / limites +- Pas d'authentification SMTP : repose sur la confiance réseau (`mynetworks` du relais). +- TLS interne désactivé pour l'instant ; activable avec la confiance step_ca (`clients_pki`). + +## Prérequis +- Dépendance `clients_smtp requiert serveurs_sendmail actif` (déjà dans `docs/dependances-groupes.yml`). diff --git a/roles/clients_smtp/defaults/main.yml b/roles/clients_smtp/defaults/main.yml new file mode 100644 index 0000000..1facfb8 --- /dev/null +++ b/roles/clients_smtp/defaults/main.yml @@ -0,0 +1,12 @@ +--- +# Relais mail local leger (msmtp) vers serveurs_sendmail. +clients_smtp_paquets: + - msmtp + - msmtp-mta # fournit /usr/sbin/sendmail + +clients_smtp_relais: "infra-mail-01.chezlepro.internal" +clients_smtp_port: 25 +clients_smtp_from: "{{ ansible_hostname }}@chezlepro.internal" + +# TLS : off sur le reseau interne pour l'instant ; on avec certificats step_ca. +clients_smtp_tls: false diff --git a/roles/clients_smtp/tasks/main.yml b/roles/clients_smtp/tasks/main.yml new file mode 100644 index 0000000..bcd35a1 --- /dev/null +++ b/roles/clients_smtp/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Installer msmtp (relais mail local) + ansible.builtin.apt: + name: "{{ clients_smtp_paquets }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Deployer la configuration msmtp + ansible.builtin.template: + src: msmtprc.j2 + dest: /etc/msmtprc + owner: root + group: root + mode: "0644" diff --git a/roles/clients_smtp/templates/msmtprc.j2 b/roles/clients_smtp/templates/msmtprc.j2 new file mode 100644 index 0000000..5b8efcc --- /dev/null +++ b/roles/clients_smtp/templates/msmtprc.j2 @@ -0,0 +1,9 @@ +# Gere par Set-OPS (role clients_smtp). Ne pas editer a la main. +defaults +tls {{ 'on' if clients_smtp_tls | bool else 'off' }} +syslog on + +account default +host {{ clients_smtp_relais }} +port {{ clients_smtp_port }} +from {{ clients_smtp_from }} diff --git a/roles/serveurs_forgejo/README.md b/roles/serveurs_forgejo/README.md new file mode 100644 index 0000000..989878d --- /dev/null +++ b/roles/serveurs_forgejo/README.md @@ -0,0 +1,34 @@ +# serveurs_forgejo + +Forge Git **Forgejo** (binaire officiel) — dépôts Git, CI, revue de code. + +## Rôle +- Installe le binaire Forgejo (version épinglée, lien symbolique courant), utilisateur `git`. +- Crée l'arborescence (`/var/lib/forgejo`, `/etc/forgejo`). +- **Base PostgreSQL via le registre** (`docs/bases-donnees.yml`, entrée `forgejo`). +- `app.ini` : serveur derrière nginx (`HTTP_ADDR=127.0.0.1`, `ROOT_URL` https), DB, secrets, mailer vers `serveurs_sendmail`, inscription désactivée, `INSTALL_LOCK` (pas d'assistant web). +- Service systemd + création du **compte administrateur** initial (une fois). + +## Secrets requis (Vault) +```yaml +serveurs_forgejo_secret_key: "{{ vault_forgejo_secret_key }}" # forgejo generate secret SECRET_KEY +serveurs_forgejo_internal_token: "{{ vault_forgejo_internal_token }}" # forgejo generate secret INTERNAL_TOKEN +serveurs_forgejo_admin_password: "{{ vault_forgejo_admin }}" +vault_bd_forgejo: "..." # mot de passe BD (partage avec serveurs_postgresql) +``` + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_forgejo_version` | `10.0.0` | Version (à vérifier/bumper) | +| `serveurs_forgejo_hostname` | `forge.chezlepro.internal` | Domaine (via nginx) | +| `serveurs_forgejo_db_host` | `10.1.13.11` | data-01 | + +## Notes / limites +- **Version** à confirmer sur forgejo.org/download ; l'URL suit le motif officiel des releases. +- Le **site nginx** publiant Forgejo se déclare côté `serveurs_nginx`. +- Mailer en SMTP simple vers le relais interne (sans TLS pour l'instant). +- Non testé live (forge-01 planifié). + +## Prérequis +- Dépendances `serveurs_postgresql`, `serveurs_nginx`, `serveurs_sendmail` (cf. `docs/dependances-groupes.yml`). diff --git a/roles/serveurs_forgejo/defaults/main.yml b/roles/serveurs_forgejo/defaults/main.yml new file mode 100644 index 0000000..538e6ed --- /dev/null +++ b/roles/serveurs_forgejo/defaults/main.yml @@ -0,0 +1,30 @@ +--- +# Version Forgejo : verifier/ajuster sur https://forgejo.org/download/ +serveurs_forgejo_version: "10.0.0" +serveurs_forgejo_url: >- + https://codeberg.org/forgejo/forgejo/releases/download/v{{ serveurs_forgejo_version }}/forgejo-{{ serveurs_forgejo_version }}-linux-amd64 + +serveurs_forgejo_binaire: "/usr/local/bin/forgejo" +serveurs_forgejo_utilisateur: "git" +serveurs_forgejo_data: "/var/lib/forgejo" +serveurs_forgejo_config_dir: "/etc/forgejo" +serveurs_forgejo_config: "/etc/forgejo/app.ini" +serveurs_forgejo_service: "forgejo" + +# Publication (derriere serveurs_nginx, TLS a l'edge). +serveurs_forgejo_hostname: "forge.chezlepro.internal" +serveurs_forgejo_http_port: 3000 + +# Base de donnees (registre docs/bases-donnees.yml). +serveurs_forgejo_db_cle: "forgejo" +serveurs_forgejo_db_host: "10.1.13.11" # data-01 + +# Relais courriel. +serveurs_forgejo_smtp: "infra-mail-01.chezlepro.internal" + +# Secrets OBLIGATOIRES (Ansible Vault). +serveurs_forgejo_secret_key: "" # {{ vault_forgejo_secret_key }} +serveurs_forgejo_internal_token: "" # {{ vault_forgejo_internal_token }} +serveurs_forgejo_admin_user: "forgejo-admin" +serveurs_forgejo_admin_email: "admin@chezlepro.internal" +serveurs_forgejo_admin_password: "" # {{ vault_forgejo_admin }} diff --git a/roles/serveurs_forgejo/handlers/main.yml b/roles/serveurs_forgejo/handlers/main.yml new file mode 100644 index 0000000..a36f320 --- /dev/null +++ b/roles/serveurs_forgejo/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Redemarrer forgejo + ansible.builtin.systemd: + name: "{{ serveurs_forgejo_service }}" + state: restarted + daemon_reload: true + listen: Redemarrer forgejo diff --git a/roles/serveurs_forgejo/tasks/main.yml b/roles/serveurs_forgejo/tasks/main.yml new file mode 100644 index 0000000..ad4dc8d --- /dev/null +++ b/roles/serveurs_forgejo/tasks/main.yml @@ -0,0 +1,115 @@ +--- +- name: Exiger les secrets Forgejo (Vault) + ansible.builtin.assert: + that: + - serveurs_forgejo_secret_key | length > 0 + - serveurs_forgejo_internal_token | length > 0 + - serveurs_forgejo_admin_password | length > 0 + fail_msg: "secret_key, internal_token et admin_password sont requis (Ansible Vault)." + +- name: Charger le registre des bases applicatives + ansible.builtin.include_vars: + file: "{{ role_path }}/../../docs/bases-donnees.yml" + +- name: Resoudre l'entree de base Forgejo + ansible.builtin.set_fact: + serveurs_forgejo_entree: "{{ (bases_donnees | default({}))[serveurs_forgejo_db_cle] | default({}) }}" + +- name: Exiger l'entree de registre et son secret + ansible.builtin.assert: + that: + - serveurs_forgejo_entree.base is defined + - serveurs_forgejo_entree.secret is defined + fail_msg: "Entree '{{ serveurs_forgejo_db_cle }}' absente de docs/bases-donnees.yml." + +- name: Resoudre le mot de passe de base (Vault) + ansible.builtin.set_fact: + serveurs_forgejo_db_password: "{{ lookup('vars', serveurs_forgejo_entree.secret) }}" + no_log: true + +- name: Creer l'utilisateur systeme git + ansible.builtin.user: + name: "{{ serveurs_forgejo_utilisateur }}" + system: true + shell: /bin/bash + home: "/home/{{ serveurs_forgejo_utilisateur }}" + create_home: true + comment: "Git Version Control" + +- name: Telecharger le binaire Forgejo + ansible.builtin.get_url: + url: "{{ serveurs_forgejo_url }}" + dest: "/usr/local/bin/forgejo-{{ serveurs_forgejo_version }}" + owner: root + group: root + mode: "0755" + +- name: Activer la version courante du binaire + ansible.builtin.file: + src: "/usr/local/bin/forgejo-{{ serveurs_forgejo_version }}" + dest: "{{ serveurs_forgejo_binaire }}" + state: link + notify: Redemarrer forgejo + +- name: Creer les repertoires de donnees + ansible.builtin.file: + path: "{{ serveurs_forgejo_data }}/{{ item }}" + state: directory + owner: "{{ serveurs_forgejo_utilisateur }}" + group: "{{ serveurs_forgejo_utilisateur }}" + mode: "0750" + loop: + - "" + - custom + - data + - log + +- name: Creer le repertoire de configuration + ansible.builtin.file: + path: "{{ serveurs_forgejo_config_dir }}" + state: directory + owner: root + group: "{{ serveurs_forgejo_utilisateur }}" + mode: "0770" + +- name: Deployer app.ini + ansible.builtin.template: + src: app.ini.j2 + dest: "{{ serveurs_forgejo_config }}" + owner: root + group: "{{ serveurs_forgejo_utilisateur }}" + mode: "0640" + no_log: true + notify: Redemarrer forgejo + +- name: Deployer l'unite systemd forgejo + ansible.builtin.template: + src: forgejo.service.j2 + dest: /etc/systemd/system/forgejo.service + owner: root + group: root + mode: "0644" + notify: Redemarrer forgejo + +- name: Activer et demarrer Forgejo + ansible.builtin.systemd: + name: "{{ serveurs_forgejo_service }}" + enabled: true + state: started + daemon_reload: true + +- name: Creer le compte administrateur (une fois) + ansible.builtin.shell: + cmd: >- + {{ serveurs_forgejo_binaire }} admin user create + --admin --username {{ serveurs_forgejo_admin_user }} + --email {{ serveurs_forgejo_admin_email }} + --password '{{ serveurs_forgejo_admin_password }}' + --config {{ serveurs_forgejo_config }} + && touch {{ serveurs_forgejo_data }}/.admin-created + creates: "{{ serveurs_forgejo_data }}/.admin-created" + become: true + become_user: "{{ serveurs_forgejo_utilisateur }}" + environment: + GITEA_WORK_DIR: "{{ serveurs_forgejo_data }}" + no_log: true diff --git a/roles/serveurs_forgejo/templates/app.ini.j2 b/roles/serveurs_forgejo/templates/app.ini.j2 new file mode 100644 index 0000000..0866029 --- /dev/null +++ b/roles/serveurs_forgejo/templates/app.ini.j2 @@ -0,0 +1,34 @@ +; Gere par Set-OPS (role serveurs_forgejo). Ne pas editer a la main. +APP_NAME = Forgejo Chezlepro +RUN_USER = {{ serveurs_forgejo_utilisateur }} +RUN_MODE = prod +WORK_PATH = {{ serveurs_forgejo_data }} + +[server] +DOMAIN = {{ serveurs_forgejo_hostname }} +ROOT_URL = https://{{ serveurs_forgejo_hostname }}/ +HTTP_ADDR = 127.0.0.1 +HTTP_PORT = {{ serveurs_forgejo_http_port }} +SSH_DOMAIN = {{ serveurs_forgejo_hostname }} + +[database] +DB_TYPE = postgres +HOST = {{ serveurs_forgejo_db_host }}:5432 +NAME = {{ serveurs_forgejo_entree.base }} +USER = {{ serveurs_forgejo_entree.proprietaire }} +PASSWD = {{ serveurs_forgejo_db_password }} + +[security] +INSTALL_LOCK = true +SECRET_KEY = {{ serveurs_forgejo_secret_key }} +INTERNAL_TOKEN = {{ serveurs_forgejo_internal_token }} + +[service] +DISABLE_REGISTRATION = true + +[mailer] +ENABLED = true +PROTOCOL = smtp +SMTP_ADDR = {{ serveurs_forgejo_smtp }} +SMTP_PORT = 25 +FROM = forge@chezlepro.internal diff --git a/roles/serveurs_forgejo/templates/forgejo.service.j2 b/roles/serveurs_forgejo/templates/forgejo.service.j2 new file mode 100644 index 0000000..d54ddbd --- /dev/null +++ b/roles/serveurs_forgejo/templates/forgejo.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Forgejo (forge Git) +Documentation=https://forgejo.org/docs/ +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User={{ serveurs_forgejo_utilisateur }} +Group={{ serveurs_forgejo_utilisateur }} +WorkingDirectory={{ serveurs_forgejo_data }} +ExecStart={{ serveurs_forgejo_binaire }} web --config {{ serveurs_forgejo_config }} +Restart=always +RestartSec=5 +Environment=USER={{ serveurs_forgejo_utilisateur }} +Environment=HOME=/home/{{ serveurs_forgejo_utilisateur }} +Environment=GITEA_WORK_DIR={{ serveurs_forgejo_data }} + +[Install] +WantedBy=multi-user.target diff --git a/roles/serveurs_grafana/README.md b/roles/serveurs_grafana/README.md new file mode 100644 index 0000000..333523f --- /dev/null +++ b/roles/serveurs_grafana/README.md @@ -0,0 +1,33 @@ +# serveurs_grafana + +Tableaux de bord **Grafana** (dépôt apt officiel Grafana). Interface d'observabilité +au-dessus de Prometheus (métriques) et Loki (journaux). + +## Rôle +- Ajoute le **dépôt apt Grafana** et installe `grafana`. +- **Provisionne les datasources** Prometheus + Loki (`/etc/grafana/provisioning/datasources/`) — Grafana les câble au démarrage. +- Domaine, `root_url` et **mot de passe admin (Vault)** via un **drop-in systemd** (non destructif, ne touche pas `grafana.ini` du paquet). +- Inscriptions publiques désactivées. + +## Publication +- Derrière `serveurs_nginx` (TLS à l'edge) ; `GF_SERVER_ROOT_URL=https://grafana.chezlepro.internal/`. +- Le site nginx se déclare côté `serveurs_nginx`. + +## Secret requis (Vault) +```yaml +serveurs_grafana_admin_password: "{{ vault_grafana_admin }}" +``` + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_grafana_hostname` | `grafana.chezlepro.internal` | Domaine | +| `serveurs_grafana_prometheus_url` | `http://localhost:9090` | Datasource Prometheus | +| `serveurs_grafana_loki_url` | `http://localhost:3100` | Datasource Loki | + +## Notes +- Prometheus, Loki et Grafana sont co-localisés sur `obs-01` → datasources en `localhost`. +- Dépendance `serveurs_grafana requiert serveurs_prometheus + serveurs_loki actifs` (déjà dans `docs/dependances-groupes.yml`). + +## Hors périmètre +- Dashboards provisionnés (à ajouter ensuite), alerting Grafana, authentification OIDC via Keycloak (intégration ultérieure). diff --git a/roles/serveurs_grafana/defaults/main.yml b/roles/serveurs_grafana/defaults/main.yml new file mode 100644 index 0000000..a95ce15 --- /dev/null +++ b/roles/serveurs_grafana/defaults/main.yml @@ -0,0 +1,22 @@ +--- +serveurs_grafana_paquets: + - grafana + +serveurs_grafana_service: "grafana-server" +serveurs_grafana_port: 3000 + +# Publication (derriere serveurs_nginx, TLS termine a l'edge). +serveurs_grafana_hostname: "grafana.chezlepro.internal" +serveurs_grafana_root_url: "https://{{ serveurs_grafana_hostname }}/" + +# Datasources (Prometheus + Loki, co-localises sur obs-01). +serveurs_grafana_prometheus_url: "http://localhost:9090" +serveurs_grafana_loki_url: "http://localhost:3100" + +# Secret OBLIGATOIRE (Ansible Vault). +serveurs_grafana_admin_password: "" # {{ vault_grafana_admin }} + +# Depot apt officiel Grafana (partage avec serveurs_loki). +serveurs_grafana_depot_cle_url: "https://apt.grafana.com/gpg-full.key" +serveurs_grafana_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc" +serveurs_grafana_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" diff --git a/roles/serveurs_grafana/handlers/main.yml b/roles/serveurs_grafana/handlers/main.yml new file mode 100644 index 0000000..7f6aa1a --- /dev/null +++ b/roles/serveurs_grafana/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Redemarrer grafana + ansible.builtin.systemd: + name: "{{ serveurs_grafana_service }}" + state: restarted + daemon_reload: true + listen: Redemarrer grafana diff --git a/roles/serveurs_grafana/tasks/main.yml b/roles/serveurs_grafana/tasks/main.yml new file mode 100644 index 0000000..39d6d7b --- /dev/null +++ b/roles/serveurs_grafana/tasks/main.yml @@ -0,0 +1,68 @@ +--- +- name: Exiger le mot de passe admin Grafana (Vault) + ansible.builtin.assert: + that: + - serveurs_grafana_admin_password | length > 0 + fail_msg: "serveurs_grafana_admin_password est requis (Ansible Vault)." + +- name: Assurer le repertoire des trousseaux apt + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + +- name: Telecharger la cle de signature Grafana + ansible.builtin.get_url: + url: "{{ serveurs_grafana_depot_cle_url }}" + dest: "{{ serveurs_grafana_depot_cle_fichier }}" + owner: root + group: root + mode: "0644" + +- name: Ajouter le depot apt Grafana + ansible.builtin.apt_repository: + repo: "{{ serveurs_grafana_depot_source }}" + filename: grafana + state: present + +- name: Installer Grafana + ansible.builtin.apt: + name: "{{ serveurs_grafana_paquets }}" + state: present + update_cache: true + +- name: Provisionner les datasources (Prometheus + Loki) + ansible.builtin.template: + src: datasources.yaml.j2 + dest: /etc/grafana/provisioning/datasources/chezlepro.yaml + owner: root + group: grafana + mode: "0640" + notify: Redemarrer grafana + +- name: Assurer le repertoire de drop-in systemd + ansible.builtin.file: + path: /etc/systemd/system/grafana-server.service.d + state: directory + owner: root + group: root + mode: "0755" + +- name: Deployer la configuration d'environnement (domaine + secret) + ansible.builtin.template: + src: chezlepro.conf.j2 + dest: /etc/systemd/system/grafana-server.service.d/chezlepro.conf + owner: root + group: root + mode: "0600" + no_log: true + notify: Redemarrer grafana + +- name: Activer et demarrer Grafana + ansible.builtin.systemd: + name: "{{ serveurs_grafana_service }}" + enabled: true + state: started + daemon_reload: true diff --git a/roles/serveurs_grafana/templates/chezlepro.conf.j2 b/roles/serveurs_grafana/templates/chezlepro.conf.j2 new file mode 100644 index 0000000..6f26c8b --- /dev/null +++ b/roles/serveurs_grafana/templates/chezlepro.conf.j2 @@ -0,0 +1,5 @@ +[Service] +Environment=GF_SERVER_DOMAIN={{ serveurs_grafana_hostname }} +Environment=GF_SERVER_ROOT_URL={{ serveurs_grafana_root_url }} +Environment=GF_SECURITY_ADMIN_PASSWORD={{ serveurs_grafana_admin_password }} +Environment=GF_USERS_ALLOW_SIGN_UP=false diff --git a/roles/serveurs_grafana/templates/datasources.yaml.j2 b/roles/serveurs_grafana/templates/datasources.yaml.j2 new file mode 100644 index 0000000..7761c3f --- /dev/null +++ b/roles/serveurs_grafana/templates/datasources.yaml.j2 @@ -0,0 +1,13 @@ +# Gere par Set-OPS (role serveurs_grafana). Ne pas editer a la main. +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: {{ serveurs_grafana_prometheus_url }} + isDefault: true + - name: Loki + type: loki + access: proxy + url: {{ serveurs_grafana_loki_url }} diff --git a/roles/serveurs_icinga/README.md b/roles/serveurs_icinga/README.md new file mode 100644 index 0000000..f9e473f --- /dev/null +++ b/roles/serveurs_icinga/README.md @@ -0,0 +1,33 @@ +# serveurs_icinga + +Supervision active **Icinga** — cœur de surveillance. Portée actuelle : **Icinga 2 + +Icinga DB** (avec son Redis dédié et sa base PostgreSQL). **Icinga Web 2 est différé** +à une phase dédiée. + +## Rôle (cœur) +- Ajoute le **dépôt apt officiel Icinga** (trousseau + source signée) et installe + `icinga2`, `icingadb`, `icingadb-redis`. +- `icinga2 api setup` + activation de la fonctionnalité `icingadb`. +- **Base PostgreSQL via le registre** (`docs/bases-donnees.yml`, entrée `icingadb`) : + PostgreSQL crée la base/compte, ce rôle importe le **schéma** et écrit + `/etc/icingadb/config.yml` (BD + Redis). + +## Base de données +Entrée registre `icingadb` (PostgreSQL sur data-01). Mot de passe partagé via Vault +(`vault_bd_icingadb`), comme Keycloak. Dépendance `serveurs_icinga requiert +serveurs_postgresql actif` (déjà dans `docs/dependances-groupes.yml`). + +## Différé (phase Icinga Web 2) +- `icingaweb2` + sa **2e base** (`icingaweb`) + PHP-FPM + vhost nginx + assistant de + configuration (jeton de setup). À faire proprement avec sa doc verbatim. + +## Limites / caveats honnêtes +- Les pages détaillées Icinga DB/Web ont renvoyé des 404 ; la **config Icinga DB** + (`config.yml`, chemin du schéma `/usr/share/icingadb/schema/pgsql/schema.sql`, port + Redis `6380`) suit la **structure documentée standard** mais **n'a pas pu être + vérifiée verbatim** — à confirmer/ajuster selon la version installée (variables prévues). +- Non testé live (mon-01 planifié). +- L'import de schéma s'exécute une seule fois (marqueur `/etc/icingadb/.schema-imported`). + +## Prérequis +- `serveurs_postgresql` actif (base `icingadb` créée), accès réseau à data-01:5432. diff --git a/roles/serveurs_icinga/defaults/main.yml b/roles/serveurs_icinga/defaults/main.yml new file mode 100644 index 0000000..11795ee --- /dev/null +++ b/roles/serveurs_icinga/defaults/main.yml @@ -0,0 +1,28 @@ +--- +serveurs_icinga_paquets: + - icinga2 + - icingadb + - icingadb-redis + - postgresql-client # pour importer le schema vers la BD distante + +serveurs_icinga_service_icinga2: "icinga2" +serveurs_icinga_service_icingadb: "icingadb" +serveurs_icinga_service_redis: "icingadb-redis" + +# Depot apt officiel Icinga (paquet trousseau + source signee). +serveurs_icinga_keyring_url: >- + https://packages.icinga.com/icinga-archive-keyring_latest+debian{{ ansible_distribution_major_version }}.deb +serveurs_icinga_depot_source: >- + deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] + https://packages.icinga.com/debian icinga-{{ ansible_distribution_release }} main + +# Base Icinga DB : entree du registre docs/bases-donnees.yml. +serveurs_icinga_db_cle: "icingadb" +serveurs_icinga_db_host: "10.1.13.11" # data-01 (serveurs_postgresql) + +# Redis dedie a Icinga DB (paquet icingadb-redis). +serveurs_icinga_redis_host: "localhost" +serveurs_icinga_redis_port: 6380 + +serveurs_icinga_config: "/etc/icingadb/config.yml" +serveurs_icinga_schema: "/usr/share/icingadb/schema/pgsql/schema.sql" diff --git a/roles/serveurs_icinga/handlers/main.yml b/roles/serveurs_icinga/handlers/main.yml new file mode 100644 index 0000000..2ecbb9c --- /dev/null +++ b/roles/serveurs_icinga/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Redemarrer icinga2 + ansible.builtin.systemd: + name: "{{ serveurs_icinga_service_icinga2 }}" + state: restarted + listen: Redemarrer icinga2 + +- name: Redemarrer icingadb + ansible.builtin.systemd: + name: "{{ serveurs_icinga_service_icingadb }}" + state: restarted + listen: Redemarrer icingadb diff --git a/roles/serveurs_icinga/tasks/main.yml b/roles/serveurs_icinga/tasks/main.yml new file mode 100644 index 0000000..86e1956 --- /dev/null +++ b/roles/serveurs_icinga/tasks/main.yml @@ -0,0 +1,90 @@ +--- +- name: Telecharger le trousseau de cles Icinga + ansible.builtin.get_url: + url: "{{ serveurs_icinga_keyring_url }}" + dest: "/tmp/icinga-archive-keyring.deb" + mode: "0644" + +- name: Installer le trousseau de cles Icinga + ansible.builtin.apt: + deb: "/tmp/icinga-archive-keyring.deb" + state: present + +- name: Ajouter le depot apt Icinga + ansible.builtin.apt_repository: + repo: "{{ serveurs_icinga_depot_source }}" + filename: icinga + state: present + +- name: Installer Icinga 2, Icinga DB et Redis dedie + ansible.builtin.apt: + name: "{{ serveurs_icinga_paquets }}" + state: present + update_cache: true + +- name: Charger le registre des bases applicatives + ansible.builtin.include_vars: + file: "{{ role_path }}/../../docs/bases-donnees.yml" + +- name: Resoudre l'entree de base Icinga DB + ansible.builtin.set_fact: + serveurs_icinga_entree: "{{ (bases_donnees | default({}))[serveurs_icinga_db_cle] | default({}) }}" + +- name: Exiger l'entree de registre et son secret + ansible.builtin.assert: + that: + - serveurs_icinga_entree.base is defined + - serveurs_icinga_entree.secret is defined + fail_msg: "Entree '{{ serveurs_icinga_db_cle }}' absente de docs/bases-donnees.yml." + +- name: Resoudre le mot de passe de base (Vault) + ansible.builtin.set_fact: + serveurs_icinga_db_password: "{{ lookup('vars', serveurs_icinga_entree.secret) }}" + no_log: true + +- name: Configurer l'API Icinga 2 + ansible.builtin.command: + cmd: icinga2 api setup + creates: /etc/icinga2/features-enabled/api.conf + notify: Redemarrer icinga2 + +- name: Activer la fonctionnalite icingadb dans Icinga 2 + ansible.builtin.command: + cmd: icinga2 feature enable icingadb + creates: /etc/icinga2/features-enabled/icingadb.conf + notify: Redemarrer icinga2 + +- name: Activer et demarrer le Redis Icinga DB + ansible.builtin.systemd: + name: "{{ serveurs_icinga_service_redis }}" + enabled: true + state: started + +- name: Importer le schema Icinga DB dans PostgreSQL (une fois) + ansible.builtin.shell: + cmd: >- + PGPASSWORD='{{ serveurs_icinga_db_password }}' + psql -h {{ serveurs_icinga_db_host }} -U {{ serveurs_icinga_entree.proprietaire }} + -d {{ serveurs_icinga_entree.base }} -f {{ serveurs_icinga_schema }} + && touch /etc/icingadb/.schema-imported + creates: /etc/icingadb/.schema-imported + no_log: true + +- name: Deployer la configuration Icinga DB + ansible.builtin.template: + src: config.yml.j2 + dest: "{{ serveurs_icinga_config }}" + owner: root + group: icingadb + mode: "0640" + no_log: true + notify: Redemarrer icingadb + +- name: Activer et demarrer Icinga 2 et Icinga DB + ansible.builtin.systemd: + name: "{{ item }}" + enabled: true + state: started + loop: + - "{{ serveurs_icinga_service_icinga2 }}" + - "{{ serveurs_icinga_service_icingadb }}" diff --git a/roles/serveurs_icinga/templates/config.yml.j2 b/roles/serveurs_icinga/templates/config.yml.j2 new file mode 100644 index 0000000..874256f --- /dev/null +++ b/roles/serveurs_icinga/templates/config.yml.j2 @@ -0,0 +1,11 @@ +# Gere par Set-OPS (role serveurs_icinga). Ne pas editer a la main. +database: + type: pgsql + host: {{ serveurs_icinga_db_host }} + database: {{ serveurs_icinga_entree.base }} + user: {{ serveurs_icinga_entree.proprietaire }} + password: {{ serveurs_icinga_db_password }} + +redis: + host: {{ serveurs_icinga_redis_host }} + port: {{ serveurs_icinga_redis_port }} diff --git a/roles/serveurs_keycloak/README.md b/roles/serveurs_keycloak/README.md new file mode 100644 index 0000000..919c36b --- /dev/null +++ b/roles/serveurs_keycloak/README.md @@ -0,0 +1,42 @@ +# serveurs_keycloak + +SSO / IAM **Keycloak** (distribution Quarkus) — hub d'authentification centralisée +(OIDC/SAML) pour les services Chezlepro. + +## Rôle +- Installe un JRE, télécharge et extrait la distribution Keycloak dans `/opt/keycloak`. +- Configure la **base PostgreSQL via le registre** `docs/bases-donnees.yml` (entrée `keycloak`). +- Publie derrière `serveurs_nginx` (TLS à l'edge) : `hostname`, `proxy-headers=xforwarded`, `http-enabled=true`. +- Secrets (mot de passe BD + admin bootstrap) dans `/etc/keycloak/keycloak.env` (`0640`, `no_log`). +- `kc.sh build --db=postgres` (une fois par version) puis service systemd `start --optimized`. + +## Premier consommateur du registre de BD +L'entrée `keycloak` de `docs/bases-donnees.yml` fait que : +1. `serveurs_postgresql` crée la base `keycloak` + le compte propriétaire `keycloak`. +2. Ce rôle lit **la même entrée** pour bâtir sa connexion (`db-url-database`, `db-username`, mot de passe = `vault_bd_keycloak`). + +Mot de passe partagé, source unique. Dépendance `serveurs_keycloak requiert serveurs_postgresql actif` déjà dans `docs/dependances-groupes.yml`. + +## Secrets requis (Vault) +```yaml +serveurs_keycloak_admin_password: "{{ vault_keycloak_admin }}" # admin bootstrap +vault_bd_keycloak: "..." # mot de passe BD (partage avec postgres) +``` + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_keycloak_version` | `26.0.7` | Version (à vérifier/bumper sur keycloak.org/downloads) | +| `serveurs_keycloak_hostname` | `keycloak.chezlepro.internal` | Hostname public (via nginx) | +| `serveurs_keycloak_db_host` | `10.1.13.11` | IP de data-01 (serveurs_postgresql) | +| `serveurs_keycloak_admin_user` | `admin` | Admin bootstrap | + +## Notes / limites +- **Version** : à confirmer/bumper ; l'URL suit le motif officiel des releases GitHub. +- L'unité systemd est dérivée des conventions Keycloak (la doc officielle ne fournit pas d'unité) — sandboxing volontairement modéré pour ne pas casser le démarrage. +- Le **site nginx** publiant Keycloak se déclare côté `serveurs_nginx` (`serveurs_nginx_sites`), pas ici. +- Certificats : TLS terminé à l'edge ; aucun certificat sur Keycloak. + +## Prérequis +- Réseau sortant pour télécharger la distribution. +- Base PostgreSQL atteignable (dépendance `serveurs_postgresql`). diff --git a/roles/serveurs_keycloak/defaults/main.yml b/roles/serveurs_keycloak/defaults/main.yml new file mode 100644 index 0000000..12c1da6 --- /dev/null +++ b/roles/serveurs_keycloak/defaults/main.yml @@ -0,0 +1,21 @@ +--- +# Distribution Keycloak (Quarkus). Verifier/ajuster la version sur keycloak.org/downloads. +serveurs_keycloak_version: "26.0.7" +serveurs_keycloak_url: "https://github.com/keycloak/keycloak/releases/download/{{ serveurs_keycloak_version }}/keycloak-{{ serveurs_keycloak_version }}.tar.gz" +serveurs_keycloak_java_paquet: "default-jre-headless" + +serveurs_keycloak_home: "/opt/keycloak" +serveurs_keycloak_utilisateur: "keycloak" +serveurs_keycloak_service: "keycloak" + +# Publication (derriere serveurs_nginx, TLS termine a l'edge). +serveurs_keycloak_hostname: "keycloak.chezlepro.internal" + +# Base de donnees : entree du registre docs/bases-donnees.yml. +serveurs_keycloak_db_cle: "keycloak" +# IP interne du serveur PostgreSQL (data-01, cf. nomenclature). +serveurs_keycloak_db_host: "10.1.13.11" + +# Secrets (Ansible Vault) — obligatoires. +serveurs_keycloak_admin_user: "admin" +serveurs_keycloak_admin_password: "" # {{ vault_keycloak_admin }} diff --git a/roles/serveurs_keycloak/handlers/main.yml b/roles/serveurs_keycloak/handlers/main.yml new file mode 100644 index 0000000..2589c52 --- /dev/null +++ b/roles/serveurs_keycloak/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Redemarrer keycloak + ansible.builtin.systemd: + name: "{{ serveurs_keycloak_service }}" + state: restarted + daemon_reload: true + listen: Redemarrer keycloak diff --git a/roles/serveurs_keycloak/tasks/main.yml b/roles/serveurs_keycloak/tasks/main.yml new file mode 100644 index 0000000..d823799 --- /dev/null +++ b/roles/serveurs_keycloak/tasks/main.yml @@ -0,0 +1,119 @@ +--- +- name: Exiger le mot de passe admin Keycloak (Vault) + ansible.builtin.assert: + that: + - serveurs_keycloak_admin_password | length > 0 + fail_msg: "serveurs_keycloak_admin_password est requis (Ansible Vault)." + +- name: Charger le registre des bases applicatives + ansible.builtin.include_vars: + file: "{{ role_path }}/../../docs/bases-donnees.yml" + +- name: Resoudre l'entree de base de donnees Keycloak + ansible.builtin.set_fact: + serveurs_keycloak_entree: "{{ (bases_donnees | default({}))[serveurs_keycloak_db_cle] | default({}) }}" + +- name: Exiger une entree de registre et son secret + ansible.builtin.assert: + that: + - serveurs_keycloak_entree.base is defined + - serveurs_keycloak_entree.proprietaire is defined + - serveurs_keycloak_entree.secret is defined + fail_msg: "Entree '{{ serveurs_keycloak_db_cle }}' absente de docs/bases-donnees.yml." + +- name: Resoudre le mot de passe de base de donnees (Vault) + ansible.builtin.set_fact: + serveurs_keycloak_db_password: "{{ lookup('vars', serveurs_keycloak_entree.secret) }}" + no_log: true + +- name: Installer le JRE + ansible.builtin.apt: + name: "{{ serveurs_keycloak_java_paquet }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Creer l'utilisateur systeme keycloak + ansible.builtin.user: + name: "{{ serveurs_keycloak_utilisateur }}" + system: true + home: "{{ serveurs_keycloak_home }}" + create_home: false + shell: /usr/sbin/nologin + +- name: Creer le repertoire d'installation + ansible.builtin.file: + path: "{{ serveurs_keycloak_home }}" + state: directory + owner: "{{ serveurs_keycloak_utilisateur }}" + group: "{{ serveurs_keycloak_utilisateur }}" + mode: "0750" + +- name: Telecharger la distribution Keycloak + ansible.builtin.get_url: + url: "{{ serveurs_keycloak_url }}" + dest: "/tmp/keycloak-{{ serveurs_keycloak_version }}.tar.gz" + mode: "0644" + +- name: Extraire Keycloak + ansible.builtin.unarchive: + src: "/tmp/keycloak-{{ serveurs_keycloak_version }}.tar.gz" + dest: "{{ serveurs_keycloak_home }}" + remote_src: true + extra_opts: ["--strip-components=1"] + owner: "{{ serveurs_keycloak_utilisateur }}" + group: "{{ serveurs_keycloak_utilisateur }}" + creates: "{{ serveurs_keycloak_home }}/bin/kc.sh" + +- name: Deployer keycloak.conf (config non sensible) + ansible.builtin.template: + src: keycloak.conf.j2 + dest: "{{ serveurs_keycloak_home }}/conf/keycloak.conf" + owner: "{{ serveurs_keycloak_utilisateur }}" + group: "{{ serveurs_keycloak_utilisateur }}" + mode: "0640" + notify: Redemarrer keycloak + +- name: Creer le repertoire de configuration des secrets + ansible.builtin.file: + path: /etc/keycloak + state: directory + owner: root + group: "{{ serveurs_keycloak_utilisateur }}" + mode: "0750" + +- name: Deployer le fichier d'environnement (secrets) + ansible.builtin.template: + src: keycloak.env.j2 + dest: /etc/keycloak/keycloak.env + owner: root + group: "{{ serveurs_keycloak_utilisateur }}" + mode: "0640" + no_log: true + notify: Redemarrer keycloak + +- name: Construire Keycloak (une fois par version) + ansible.builtin.shell: + cmd: >- + {{ serveurs_keycloak_home }}/bin/kc.sh build --db=postgres + && touch {{ serveurs_keycloak_home }}/.kc-built-{{ serveurs_keycloak_version }} + creates: "{{ serveurs_keycloak_home }}/.kc-built-{{ serveurs_keycloak_version }}" + become: true + become_user: "{{ serveurs_keycloak_utilisateur }}" + notify: Redemarrer keycloak + +- name: Deployer l'unite systemd keycloak + ansible.builtin.template: + src: keycloak.service.j2 + dest: /etc/systemd/system/keycloak.service + owner: root + group: root + mode: "0644" + notify: Redemarrer keycloak + +- name: Activer et demarrer keycloak + ansible.builtin.systemd: + name: "{{ serveurs_keycloak_service }}" + enabled: true + state: started + daemon_reload: true diff --git a/roles/serveurs_keycloak/templates/keycloak.conf.j2 b/roles/serveurs_keycloak/templates/keycloak.conf.j2 new file mode 100644 index 0000000..5a9eb9f --- /dev/null +++ b/roles/serveurs_keycloak/templates/keycloak.conf.j2 @@ -0,0 +1,10 @@ +# Gere par Set-OPS (role serveurs_keycloak). Ne pas editer a la main. +db=postgres +db-url-host={{ serveurs_keycloak_db_host }} +db-url-database={{ serveurs_keycloak_entree.base }} +db-username={{ serveurs_keycloak_entree.proprietaire }} + +# Derriere serveurs_nginx (TLS termine a l'edge). +hostname={{ serveurs_keycloak_hostname }} +proxy-headers=xforwarded +http-enabled=true diff --git a/roles/serveurs_keycloak/templates/keycloak.env.j2 b/roles/serveurs_keycloak/templates/keycloak.env.j2 new file mode 100644 index 0000000..bc19d34 --- /dev/null +++ b/roles/serveurs_keycloak/templates/keycloak.env.j2 @@ -0,0 +1,4 @@ +# Gere par Set-OPS (role serveurs_keycloak). Secrets — ne pas editer a la main. +KC_DB_PASSWORD={{ serveurs_keycloak_db_password }} +KC_BOOTSTRAP_ADMIN_USERNAME={{ serveurs_keycloak_admin_user }} +KC_BOOTSTRAP_ADMIN_PASSWORD={{ serveurs_keycloak_admin_password }} diff --git a/roles/serveurs_keycloak/templates/keycloak.service.j2 b/roles/serveurs_keycloak/templates/keycloak.service.j2 new file mode 100644 index 0000000..fc3be26 --- /dev/null +++ b/roles/serveurs_keycloak/templates/keycloak.service.j2 @@ -0,0 +1,24 @@ +[Unit] +Description=Keycloak Identity and Access Management +Documentation=https://www.keycloak.org/server/configuration +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User={{ serveurs_keycloak_utilisateur }} +Group={{ serveurs_keycloak_utilisateur }} +EnvironmentFile=/etc/keycloak/keycloak.env +WorkingDirectory={{ serveurs_keycloak_home }} +ExecStart={{ serveurs_keycloak_home }}/bin/kc.sh start --optimized +Restart=on-failure +RestartSec=5 +TimeoutStartSec=120 +LimitNOFILE=102642 +NoNewPrivileges=yes +ProtectSystem=full +ProtectHome=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/roles/serveurs_loki/README.md b/roles/serveurs_loki/README.md new file mode 100644 index 0000000..d0ff5fe --- /dev/null +++ b/roles/serveurs_loki/README.md @@ -0,0 +1,30 @@ +# serveurs_loki + +Agrégation de journaux **Loki** (dépôt apt officiel Grafana), plateforme d'observabilité. + +## Rôle +- Ajoute le **dépôt apt Grafana** (clé + source) et installe `loki`. +- Déploie une configuration **single-node** (stockage `filesystem`, schéma TSDB v13). +- Rétention via le compactor (`retention_enabled`, `retention_period`). +- Écoute sur `:3100`. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_loki_port` | `3100` | Port HTTP | +| `serveurs_loki_chemin` | `/var/lib/loki` | Stockage local | +| `serveurs_loki_retention` | `744h` (31 j) | Rétention des journaux | + +## Intégration +- Côté VM : l'expédition des journaux (promtail / Grafana Alloy) sera gérée par le + futur rôle `clients_journaux`. +- `serveurs_grafana` consommera Loki comme datasource. + +## Notes / limites +- Le format de configuration Loki **évolue entre versions** ; la config fournie cible + un Loki récent (TSDB v13, single-node). Un ajustement mineur peut être requis selon + la version installée par le dépôt. +- Le chemin du fichier de config (`/etc/loki/config.yml`) suit le paquet Debian Grafana. + +## Hors périmètre +- Mode distribué/microservices, stockage objet (S3), alerting Loki. diff --git a/roles/serveurs_loki/defaults/main.yml b/roles/serveurs_loki/defaults/main.yml new file mode 100644 index 0000000..b9bc378 --- /dev/null +++ b/roles/serveurs_loki/defaults/main.yml @@ -0,0 +1,14 @@ +--- +serveurs_loki_paquets: + - loki + +serveurs_loki_service: "loki" +serveurs_loki_config: "/etc/loki/config.yml" +serveurs_loki_port: 3100 +serveurs_loki_chemin: "/var/lib/loki" +serveurs_loki_retention: "744h" # 31 jours + +# Depot apt officiel Grafana (partage avec serveurs_grafana). +serveurs_loki_depot_cle_url: "https://apt.grafana.com/gpg-full.key" +serveurs_loki_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc" +serveurs_loki_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" diff --git a/roles/serveurs_loki/handlers/main.yml b/roles/serveurs_loki/handlers/main.yml new file mode 100644 index 0000000..5a84b8b --- /dev/null +++ b/roles/serveurs_loki/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Redemarrer loki + ansible.builtin.systemd: + name: "{{ serveurs_loki_service }}" + state: restarted + listen: Redemarrer loki diff --git a/roles/serveurs_loki/tasks/main.yml b/roles/serveurs_loki/tasks/main.yml new file mode 100644 index 0000000..a40c9d4 --- /dev/null +++ b/roles/serveurs_loki/tasks/main.yml @@ -0,0 +1,51 @@ +--- +- name: Assurer le repertoire des trousseaux apt + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + +- name: Telecharger la cle de signature Grafana + ansible.builtin.get_url: + url: "{{ serveurs_loki_depot_cle_url }}" + dest: "{{ serveurs_loki_depot_cle_fichier }}" + owner: root + group: root + mode: "0644" + +- name: Ajouter le depot apt Grafana + ansible.builtin.apt_repository: + repo: "{{ serveurs_loki_depot_source }}" + filename: grafana + state: present + +- name: Installer Loki + ansible.builtin.apt: + name: "{{ serveurs_loki_paquets }}" + state: present + update_cache: true + +- name: Assurer le repertoire de donnees Loki + ansible.builtin.file: + path: "{{ serveurs_loki_chemin }}" + state: directory + owner: loki + group: loki + mode: "0750" + +- name: Deployer la configuration Loki + ansible.builtin.template: + src: config.yml.j2 + dest: "{{ serveurs_loki_config }}" + owner: root + group: loki + mode: "0640" + notify: Redemarrer loki + +- name: Activer et demarrer Loki + ansible.builtin.systemd: + name: "{{ serveurs_loki_service }}" + enabled: true + state: started diff --git a/roles/serveurs_loki/templates/config.yml.j2 b/roles/serveurs_loki/templates/config.yml.j2 new file mode 100644 index 0000000..7fe7bf9 --- /dev/null +++ b/roles/serveurs_loki/templates/config.yml.j2 @@ -0,0 +1,35 @@ +# Gere par Set-OPS (role serveurs_loki). Ne pas editer a la main. +auth_enabled: false + +server: + http_listen_port: {{ serveurs_loki_port }} + grpc_listen_port: 9096 + +common: + path_prefix: {{ serveurs_loki_chemin }} + storage: + filesystem: + chunks_directory: {{ serveurs_loki_chemin }}/chunks + rules_directory: {{ serveurs_loki_chemin }}/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2024-01-01 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +compactor: + working_directory: {{ serveurs_loki_chemin }}/compactor + retention_enabled: true + delete_request_store: filesystem + +limits_config: + retention_period: {{ serveurs_loki_retention }} diff --git a/roles/serveurs_nginx/README.md b/roles/serveurs_nginx/README.md new file mode 100644 index 0000000..100cd83 --- /dev/null +++ b/roles/serveurs_nginx/README.md @@ -0,0 +1,37 @@ +# serveurs_nginx + +Installe et configure nginx comme **edge** interne : reverse proxy et terminaison TLS. + +## Rôle +- Installe `nginx` et `ssl-cert` (certificat auto-signé). +- Durcissement de base via `conf.d/99-chezlepro.conf` (`server_tokens off`, TLS 1.2/1.3, taille de corps). +- Publie des sites de reverse proxy déclarés dans `serveurs_nginx_sites` (vide par défaut). +- **Valide la configuration (`nginx -t`) avant de recharger** (handler `Valider et recharger nginx`). + +## TLS +- Par défaut, certificat auto-signé Debian (`snakeoil`) — l'edge répond en HTTPS dès l'installation. +- `serveurs_nginx_certificat` / `serveurs_nginx_cle` (ou par site) permettent de basculer vers les certificats de l'AC interne / ACME quand `serveurs_step_ca` existera. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_nginx_certificat` / `_cle` | snakeoil | Certificat TLS par défaut | +| `serveurs_nginx_ssl_protocols` | `TLSv1.2 TLSv1.3` | Protocoles TLS | +| `serveurs_nginx_server_tokens` | `off` | Masque la version | +| `serveurs_nginx_taille_max_corps` | `16m` | `client_max_body_size` | +| `serveurs_nginx_redirection_http` | `true` | Redirige 80 → 443 | +| `serveurs_nginx_desactiver_defaut` | `false` | Retire le site Debian par défaut | +| `serveurs_nginx_sites` | `[]` | Sites reverse proxy | + +## Déclarer un site +```yaml +serveurs_nginx_sites: + - nom: forge + domaine: forge.chezlepro.internal + amont: "http://10.1.15.11:3000" + # certificat / cle optionnels (sinon snakeoil) +``` + +## Hors périmètre +- WAF (ModSecurity) — lift externe, à traiter dans une phase dédiée. +- Émission automatique des certificats (dépend de `serveurs_step_ca`). diff --git a/roles/serveurs_nginx/defaults/main.yml b/roles/serveurs_nginx/defaults/main.yml new file mode 100644 index 0000000..f319577 --- /dev/null +++ b/roles/serveurs_nginx/defaults/main.yml @@ -0,0 +1,28 @@ +--- +serveurs_nginx_packages: + - nginx + - ssl-cert # fournit le certificat auto-signe (snakeoil) + +serveurs_nginx_service_name: "nginx" + +# TLS : par defaut le certificat auto-signe Debian (snakeoil). +# A remplacer par des certificats de l'AC interne / ACME (step_ca) plus tard. +serveurs_nginx_certificat: "/etc/ssl/certs/ssl-cert-snakeoil.pem" +serveurs_nginx_cle: "/etc/ssl/private/ssl-cert-snakeoil.key" + +# Durcissement de base. +serveurs_nginx_server_tokens: "off" +serveurs_nginx_ssl_protocols: "TLSv1.2 TLSv1.3" +serveurs_nginx_taille_max_corps: "16m" + +# Comportement des sites. +serveurs_nginx_redirection_http: true # 80 -> 443 +serveurs_nginx_desactiver_defaut: false # retirer le site Debian par defaut + +# Sites de reverse proxy. Vide par defaut : aucun site publie. +serveurs_nginx_sites: [] +# - nom: forge +# domaine: forge.chezlepro.internal +# amont: "http://10.1.15.11:3000" +# certificat: "/etc/ssl/.../forge.pem" # optionnel, sinon snakeoil +# cle: "/etc/ssl/.../forge.key" diff --git a/roles/serveurs_nginx/handlers/main.yml b/roles/serveurs_nginx/handlers/main.yml new file mode 100644 index 0000000..1401af3 --- /dev/null +++ b/roles/serveurs_nginx/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Valider la configuration nginx + ansible.builtin.command: + cmd: nginx -t + changed_when: false + listen: Valider et recharger nginx + +- name: Recharger nginx + ansible.builtin.systemd: + name: "{{ serveurs_nginx_service_name }}" + state: reloaded + listen: Valider et recharger nginx diff --git a/roles/serveurs_nginx/tasks/main.yml b/roles/serveurs_nginx/tasks/main.yml new file mode 100644 index 0000000..7f232bc --- /dev/null +++ b/roles/serveurs_nginx/tasks/main.yml @@ -0,0 +1,51 @@ +--- +- name: Installer nginx + ansible.builtin.apt: + name: "{{ serveurs_nginx_packages }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Deployer le durcissement de base nginx + ansible.builtin.template: + src: 99-chezlepro.conf.j2 + dest: /etc/nginx/conf.d/99-chezlepro.conf + owner: root + group: root + mode: "0644" + notify: Valider et recharger nginx + +- name: Desactiver le site Debian par defaut + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + when: serveurs_nginx_desactiver_defaut | bool + notify: Valider et recharger nginx + +- name: Deployer les sites reverse proxy + ansible.builtin.template: + src: site.conf.j2 + dest: "/etc/nginx/sites-available/{{ item.nom }}.conf" + owner: root + group: root + mode: "0644" + loop: "{{ serveurs_nginx_sites }}" + loop_control: + label: "{{ item.nom }}" + notify: Valider et recharger nginx + +- name: Activer les sites reverse proxy + ansible.builtin.file: + src: "/etc/nginx/sites-available/{{ item.nom }}.conf" + dest: "/etc/nginx/sites-enabled/{{ item.nom }}.conf" + state: link + loop: "{{ serveurs_nginx_sites }}" + loop_control: + label: "{{ item.nom }}" + notify: Valider et recharger nginx + +- name: Activer et demarrer nginx + ansible.builtin.systemd: + name: "{{ serveurs_nginx_service_name }}" + enabled: true + state: started diff --git a/roles/serveurs_nginx/templates/99-chezlepro.conf.j2 b/roles/serveurs_nginx/templates/99-chezlepro.conf.j2 new file mode 100644 index 0000000..15cb457 --- /dev/null +++ b/roles/serveurs_nginx/templates/99-chezlepro.conf.j2 @@ -0,0 +1,4 @@ +# Gere par Set-OPS (role serveurs_nginx). Ne pas editer a la main. +server_tokens {{ serveurs_nginx_server_tokens }}; +ssl_protocols {{ serveurs_nginx_ssl_protocols }}; +client_max_body_size {{ serveurs_nginx_taille_max_corps }}; diff --git a/roles/serveurs_nginx/templates/site.conf.j2 b/roles/serveurs_nginx/templates/site.conf.j2 new file mode 100644 index 0000000..354ac7a --- /dev/null +++ b/roles/serveurs_nginx/templates/site.conf.j2 @@ -0,0 +1,26 @@ +# {{ item.nom }} — gere par Set-OPS (role serveurs_nginx). Ne pas editer a la main. +{% if serveurs_nginx_redirection_http | bool %} +server { + listen 80; + listen [::]:80; + server_name {{ item.domaine }}; + return 301 https://$host$request_uri; +} +{% endif %} +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name {{ item.domaine }}; + + ssl_certificate {{ item.certificat | default(serveurs_nginx_certificat) }}; + ssl_certificate_key {{ item.cle | default(serveurs_nginx_cle) }}; + + location / { + proxy_pass {{ item.amont }}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/roles/serveurs_openldap/README.md b/roles/serveurs_openldap/README.md new file mode 100644 index 0000000..43e813d --- /dev/null +++ b/roles/serveurs_openldap/README.md @@ -0,0 +1,38 @@ +# serveurs_openldap + +Annuaire interne OpenLDAP (slapd Debian), socle d'identité pour `clients_ldap` +et fédération applicative (Keycloak). + +## Rôle +- Préconfigure slapd via **debconf** (installation non interactive) : domaine, organisation, backend MDB. +- Installe `slapd`, `ldap-utils`, `python3-ldap`. +- Mot de passe admin depuis **Vault**. +- Crée les unités organisationnelles de base (`ou=people`, `ou=groups`). + +## Identité +- Domaine `chezlepro.internal` → base DN **`dc=chezlepro,dc=internal`** (dérivée). +- Admin : `cn=admin,dc=chezlepro,dc=internal`. + +## Secret requis (Vault) +```yaml +serveurs_openldap_admin_password: "{{ vault_openldap_admin }}" +``` +À placer dans un `group_vars/serveurs_openldap/vault.yml` chiffré. Sans lui, le rôle +refuse de s'exécuter (assertion explicite). + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_openldap_domaine` | `chezlepro.internal` | Domaine → base DN | +| `serveurs_openldap_organisation` | `Chezlepro Inc` | Organisation | +| `serveurs_openldap_ou` | `[people, groups]` | OU de base créées | + +## Notes / limites +- Le préseed debconf configure slapd à l'**installation initiale**. Reconfigurer un + slapd déjà installé (changer domaine/mot de passe) nécessite `dpkg-reconfigure slapd` + manuel (non automatisé ici pour éviter une purge accidentelle de la base). +- TLS (LDAPS) : hors périmètre du socle — viendra avec les certificats de `serveurs_step_ca`. +- Entrées utilisateurs/groupes : gérées ailleurs (identité / `clients_ldap`). + +## Prérequis +- Collection `community.general` (`ldap_entry`) + `python3-ldap` sur la cible. diff --git a/roles/serveurs_openldap/defaults/main.yml b/roles/serveurs_openldap/defaults/main.yml new file mode 100644 index 0000000..80ac4ab --- /dev/null +++ b/roles/serveurs_openldap/defaults/main.yml @@ -0,0 +1,22 @@ +--- +serveurs_openldap_paquets: + - slapd + - ldap-utils + - python3-ldap # requis par community.general.ldap_entry sur la cible + +serveurs_openldap_service: "slapd" + +# Identite de l'annuaire. +serveurs_openldap_domaine: "chezlepro.internal" +serveurs_openldap_organisation: "Chezlepro Inc" +# Base DN derivee du domaine : chezlepro.internal -> dc=chezlepro,dc=internal +serveurs_openldap_base_dn: "dc={{ serveurs_openldap_domaine.split('.') | join(',dc=') }}" +serveurs_openldap_admin_dn: "cn=admin,{{ serveurs_openldap_base_dn }}" + +# Secret OBLIGATOIRE (Ansible Vault). +serveurs_openldap_admin_password: "" # {{ vault_openldap_admin }} + +# Unites organisationnelles de base. +serveurs_openldap_ou: + - people + - groups diff --git a/roles/serveurs_openldap/tasks/main.yml b/roles/serveurs_openldap/tasks/main.yml new file mode 100644 index 0000000..2589688 --- /dev/null +++ b/roles/serveurs_openldap/tasks/main.yml @@ -0,0 +1,59 @@ +--- +- name: Exiger le mot de passe admin LDAP (Vault) + ansible.builtin.assert: + that: + - serveurs_openldap_admin_password | length > 0 + fail_msg: "serveurs_openldap_admin_password est requis (a fournir via Ansible Vault)." + +- name: Preconfigurer slapd (debconf) + ansible.builtin.debconf: + name: slapd + question: "{{ item.question }}" + vtype: "{{ item.vtype }}" + value: "{{ item.value }}" + loop: + - { question: "slapd/no_configuration", vtype: "boolean", value: "false" } + - { question: "slapd/domain", vtype: "string", value: "{{ serveurs_openldap_domaine }}" } + - { question: "shared/organization", vtype: "string", value: "{{ serveurs_openldap_organisation }}" } + - { question: "slapd/backend", vtype: "select", value: "MDB" } + - { question: "slapd/purge_database", vtype: "boolean", value: "true" } + - { question: "slapd/move_old_database", vtype: "boolean", value: "true" } + - { question: "slapd/allow_ldap_v2", vtype: "boolean", value: "false" } + loop_control: + label: "{{ item.question }}" + +- name: Preconfigurer le mot de passe admin slapd (debconf) + ansible.builtin.debconf: + name: slapd + question: "{{ item }}" + vtype: password + value: "{{ serveurs_openldap_admin_password }}" + loop: + - slapd/password1 + - slapd/password2 + no_log: true + +- name: Installer OpenLDAP + ansible.builtin.apt: + name: "{{ serveurs_openldap_paquets }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Activer et demarrer slapd + ansible.builtin.systemd: + name: "{{ serveurs_openldap_service }}" + enabled: true + state: started + +- name: Creer les unites organisationnelles de base + community.general.ldap_entry: + dn: "ou={{ item }},{{ serveurs_openldap_base_dn }}" + objectClass: organizationalUnit + server_uri: "ldapi:///" + bind_dn: "{{ serveurs_openldap_admin_dn }}" + bind_pw: "{{ serveurs_openldap_admin_password }}" + loop: "{{ serveurs_openldap_ou }}" + loop_control: + label: "ou={{ item }}" + no_log: true diff --git a/roles/serveurs_postgresql/README.md b/roles/serveurs_postgresql/README.md new file mode 100644 index 0000000..44c716a --- /dev/null +++ b/roles/serveurs_postgresql/README.md @@ -0,0 +1,55 @@ +# serveurs_postgresql + +Installe et configure PostgreSQL (paquet Debian 13) comme base relationnelle partagée interne. + +## Rôle +- Installe `postgresql`, `postgresql-contrib` et `python3-psycopg2`. +- Détecte le cluster installé par Debian (`/etc/postgresql//main`). +- Configure via un fichier `conf.d/99-chezlepro.conf` : `listen_addresses`, `port`, `password_encryption`. +- Gère `pg_hba.conf` : socket local en `peer` (administration), boucle locale et **réseaux internes autorisés** en `scram-sha-256`. +- Active et démarre le service. +- Provisionne optionnellement comptes et bases applicatifs (vides par défaut). + +## Réseau et sécurité +- Écoute par défaut sur `127.0.0.1` et l'IP interne de l'hôte (`ansible_host`). +- `serveurs_postgresql_reseaux_autorises` ouvre l'accès aux clients internes (défaut `10.1.0.0/16`). +- Authentification `scram-sha-256` (jamais `md5`/`trust`). +- `pg_hba.conf` et la conf sont **entièrement gérés** par le rôle ; les modifications manuelles sont écrasées. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_postgresql_version` | `""` (auto) | Version majeure du cluster | +| `serveurs_postgresql_port` | `5432` | Port d'écoute | +| `serveurs_postgresql_ecoute` | `127.0.0.1` + IP interne | `listen_addresses` | +| `serveurs_postgresql_reseaux_autorises` | `["10.1.0.0/16"]` | Réseaux clients en `pg_hba` | +| `serveurs_postgresql_methode_auth` | `scram-sha-256` | Méthode d'authentification | +| `serveurs_postgresql_parametres` | `{}` | Réglages additionnels | +| `serveurs_postgresql_comptes` | `[]` | Comptes applicatifs (mot de passe via Vault) | +| `serveurs_postgresql_bases` | `[]` | Bases applicatives | + +## Provisionnement applicatif +À déclarer quand un service en a besoin, mots de passe **depuis Ansible Vault** : + +```yaml +serveurs_postgresql_comptes: + - nom: keycloak + mot_de_passe: "{{ vault_postgresql_keycloak }}" +serveurs_postgresql_bases: + - nom: keycloak + proprietaire: keycloak +``` + +## Registre partagé des bases applicatives +En plus des listes explicites, le rôle lit **`docs/bases-donnees.yml`** (registre +déclaratif) et crée chaque base + son compte propriétaire dont `serveur` vaut +`serveurs_postgresql`. Le mot de passe vient de la variable Vault nommée par le +champ `secret`. Le rôle applicatif lit le même registre pour bâtir sa chaîne de +connexion — secret partagé, source unique. Patron : 1 appli → 1 base → 1 owner → +1 chaîne de connexion. Voir l'en-tête de `docs/bases-donnees.yml`. + +## Prérequis +- Collection `community.postgresql` (`ansible-galaxy collection install community.postgresql`). + +## Hors périmètre +- Sauvegardes (rôle/concern séparé), réplication, tuning avancé. diff --git a/roles/serveurs_postgresql/defaults/main.yml b/roles/serveurs_postgresql/defaults/main.yml new file mode 100644 index 0000000..2029fd9 --- /dev/null +++ b/roles/serveurs_postgresql/defaults/main.yml @@ -0,0 +1,38 @@ +--- +serveurs_postgresql_packages: + - postgresql + - postgresql-contrib + - python3-psycopg2 # requis par community.postgresql sur la cible + +serveurs_postgresql_service_name: "postgresql" + +# Version majeure du cluster. Vide => detection automatique du cluster installe +# par Debian (repertoire sous /etc/postgresql//main). +serveurs_postgresql_version: "" + +# Reseau et acces. +serveurs_postgresql_port: 5432 +serveurs_postgresql_ecoute: + - "127.0.0.1" + - "{{ ansible_host | default(ansible_default_ipv4.address, true) }}" +serveurs_postgresql_reseaux_autorises: + - "10.1.0.0/16" +serveurs_postgresql_methode_auth: "scram-sha-256" + +# Reglages additionnels (vide => valeurs par defaut de Debian). +# Exemple : { shared_buffers: "256MB", work_mem: "16MB" } +serveurs_postgresql_parametres: {} + +# Nom du groupe serveur (pour filtrer le registre des bases applicatives). +# Le registre lui-meme (bases_donnees) est charge depuis docs/bases-donnees.yml. +serveurs_postgresql_groupe: "serveurs_postgresql" + +# Provisioning applicatif explicite. Vide par defaut : aucune base ni compte cree. +# Les mots de passe doivent venir d'Ansible Vault, jamais en clair. +serveurs_postgresql_comptes: [] +# - nom: keycloak +# mot_de_passe: "{{ vault_postgresql_keycloak }}" +# attributs: "LOGIN" +serveurs_postgresql_bases: [] +# - nom: keycloak +# proprietaire: keycloak diff --git a/roles/serveurs_postgresql/handlers/main.yml b/roles/serveurs_postgresql/handlers/main.yml new file mode 100644 index 0000000..e9886bb --- /dev/null +++ b/roles/serveurs_postgresql/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Recharger PostgreSQL + ansible.builtin.systemd: + name: "{{ serveurs_postgresql_service_name }}" + state: reloaded + +- name: Redemarrer PostgreSQL + ansible.builtin.systemd: + name: "{{ serveurs_postgresql_service_name }}" + state: restarted diff --git a/roles/serveurs_postgresql/tasks/main.yml b/roles/serveurs_postgresql/tasks/main.yml new file mode 100644 index 0000000..f06ebcf --- /dev/null +++ b/roles/serveurs_postgresql/tasks/main.yml @@ -0,0 +1,134 @@ +--- +- name: Installer PostgreSQL + ansible.builtin.apt: + name: "{{ serveurs_postgresql_packages }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Lister les clusters PostgreSQL installes + ansible.builtin.find: + paths: /etc/postgresql + file_type: directory + recurse: false + register: serveurs_postgresql_clusters + +- name: Determiner la version du cluster + ansible.builtin.set_fact: + serveurs_postgresql_version_effective: >- + {{ serveurs_postgresql_version + or (serveurs_postgresql_clusters.files | map(attribute='path') | map('basename') | sort | last | default('')) }} + +- name: Refuser si aucun cluster PostgreSQL n'est present + ansible.builtin.assert: + that: + - serveurs_postgresql_version_effective | length > 0 + fail_msg: "Aucun cluster PostgreSQL trouve sous /etc/postgresql." + +- name: Definir le repertoire de configuration du cluster + ansible.builtin.set_fact: + serveurs_postgresql_conf_dir: "/etc/postgresql/{{ serveurs_postgresql_version_effective }}/main" + +- name: Assurer le repertoire conf.d + ansible.builtin.file: + path: "{{ serveurs_postgresql_conf_dir }}/conf.d" + state: directory + owner: postgres + group: postgres + mode: "0755" + +- name: Deployer la configuration Chezlepro + ansible.builtin.template: + src: 99-chezlepro.conf.j2 + dest: "{{ serveurs_postgresql_conf_dir }}/conf.d/99-chezlepro.conf" + owner: postgres + group: postgres + mode: "0640" + notify: Redemarrer PostgreSQL + +- name: Deployer pg_hba.conf + ansible.builtin.template: + src: pg_hba.conf.j2 + dest: "{{ serveurs_postgresql_conf_dir }}/pg_hba.conf" + owner: postgres + group: postgres + mode: "0640" + notify: Recharger PostgreSQL + +- name: Activer et demarrer PostgreSQL + ansible.builtin.systemd: + name: "{{ serveurs_postgresql_service_name }}" + enabled: true + state: started + +- name: Appliquer la configuration en attente avant le provisioning + ansible.builtin.meta: flush_handlers + +- name: Creer les comptes applicatifs PostgreSQL + community.postgresql.postgresql_user: + name: "{{ item.nom }}" + password: "{{ item.mot_de_passe }}" + role_attr_flags: "{{ item.attributs | default('LOGIN') }}" + state: present + become: true + become_user: postgres + loop: "{{ serveurs_postgresql_comptes }}" + loop_control: + label: "{{ item.nom }}" + no_log: true + when: serveurs_postgresql_comptes | length > 0 + +- name: Creer les bases applicatives PostgreSQL + community.postgresql.postgresql_db: + name: "{{ item.nom }}" + owner: "{{ item.proprietaire | default(item.nom) }}" + encoding: "{{ item.encodage | default('UTF8') }}" + state: present + become: true + become_user: postgres + loop: "{{ serveurs_postgresql_bases }}" + loop_control: + label: "{{ item.nom }}" + when: serveurs_postgresql_bases | length > 0 + +# --- Provisioning depuis le registre partage docs/bases-donnees.yml --- + +- name: Charger le registre des bases applicatives + ansible.builtin.include_vars: + file: "{{ role_path }}/../../docs/bases-donnees.yml" + failed_when: false + +- name: Selectionner les bases hebergees par ce serveur + ansible.builtin.set_fact: + serveurs_postgresql_registre: >- + {{ (bases_donnees | default({})) | dict2items + | selectattr('value.serveur', 'defined') + | selectattr('value.serveur', 'equalto', serveurs_postgresql_groupe) + | list }} + +- name: Creer les comptes proprietaires (registre) + community.postgresql.postgresql_user: + name: "{{ item.value.proprietaire }}" + password: "{{ lookup('vars', item.value.secret) }}" + role_attr_flags: "LOGIN" + state: present + become: true + become_user: postgres + loop: "{{ serveurs_postgresql_registre }}" + loop_control: + label: "{{ item.value.proprietaire }}" + no_log: true + when: serveurs_postgresql_registre | length > 0 + +- name: Creer les bases applicatives (registre) + community.postgresql.postgresql_db: + name: "{{ item.value.base }}" + owner: "{{ item.value.proprietaire }}" + encoding: UTF8 + state: present + become: true + become_user: postgres + loop: "{{ serveurs_postgresql_registre }}" + loop_control: + label: "{{ item.value.base }}" + when: serveurs_postgresql_registre | length > 0 diff --git a/roles/serveurs_postgresql/templates/99-chezlepro.conf.j2 b/roles/serveurs_postgresql/templates/99-chezlepro.conf.j2 new file mode 100644 index 0000000..dcf6609 --- /dev/null +++ b/roles/serveurs_postgresql/templates/99-chezlepro.conf.j2 @@ -0,0 +1,7 @@ +# Gere par Set-OPS (role serveurs_postgresql). Ne pas editer a la main. +listen_addresses = '{{ serveurs_postgresql_ecoute | join(",") }}' +port = {{ serveurs_postgresql_port }} +password_encryption = '{{ serveurs_postgresql_methode_auth }}' +{% for cle, valeur in serveurs_postgresql_parametres.items() %} +{{ cle }} = '{{ valeur }}' +{% endfor %} diff --git a/roles/serveurs_postgresql/templates/pg_hba.conf.j2 b/roles/serveurs_postgresql/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..dea8c64 --- /dev/null +++ b/roles/serveurs_postgresql/templates/pg_hba.conf.j2 @@ -0,0 +1,11 @@ +# Gere par Set-OPS (role serveurs_postgresql). Ne pas editer a la main. +# Acces administrateur local par socket (requis par Ansible/postgres). +local all postgres peer +local all all peer +# Boucle locale. +host all all 127.0.0.1/32 {{ serveurs_postgresql_methode_auth }} +host all all ::1/128 {{ serveurs_postgresql_methode_auth }} +# Reseaux internes autorises (clients applicatifs). +{% for reseau in serveurs_postgresql_reseaux_autorises %} +host all all {{ reseau }} {{ serveurs_postgresql_methode_auth }} +{% endfor %} diff --git a/roles/serveurs_prometheus/README.md b/roles/serveurs_prometheus/README.md new file mode 100644 index 0000000..78980b3 --- /dev/null +++ b/roles/serveurs_prometheus/README.md @@ -0,0 +1,31 @@ +# serveurs_prometheus + +Collecte de métriques **Prometheus** (paquet Debian), plateforme d'observabilité. + +## Rôle +- Installe `prometheus`. +- Déploie `/etc/prometheus/prometheus.yml` : job `prometheus` (soi-même) + job `node` + dont les **cibles sont dérivées de l'inventaire** (hôtes actifs de `clients_metriques` + → `node_exporter` sur `:9100`). +- Rétention via `/etc/default/prometheus` (`--storage.tsdb.retention.time`). +- **`promtool check config` avant rechargement** (handler `Valider et recharger prometheus`). + +## Cibles dérivées de l'inventaire +Comme la zone DNS, la liste de scrape se construit toute seule : ajouter un hôte au +groupe `clients_metriques` et l'activer suffit à le faire scraper. Aucun edit manuel. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_prometheus_intervalle` | `15s` | `scrape_interval` | +| `serveurs_prometheus_retention` | `15d` | Rétention TSDB | +| `serveurs_prometheus_groupe_metriques` | `clients_metriques` | Groupe source des cibles | +| `serveurs_prometheus_port_node` | `9100` | Port node_exporter | +| `serveurs_prometheus_cibles_supplementaires` | `[]` | Jobs additionnels libres | + +## Intégration +- Côté VM : `node_exporter` est installé par le futur rôle `clients_metriques`. +- `serveurs_grafana` consommera Prometheus comme datasource. + +## Hors périmètre +- Alerting (Alertmanager), règles d'alerte, fédération — phases dédiées. diff --git a/roles/serveurs_prometheus/defaults/main.yml b/roles/serveurs_prometheus/defaults/main.yml new file mode 100644 index 0000000..d5a455a --- /dev/null +++ b/roles/serveurs_prometheus/defaults/main.yml @@ -0,0 +1,16 @@ +--- +serveurs_prometheus_paquets: + - prometheus + +serveurs_prometheus_service: "prometheus" +serveurs_prometheus_config: "/etc/prometheus/prometheus.yml" + +serveurs_prometheus_intervalle: "15s" +serveurs_prometheus_retention: "15d" + +# Cibles node_exporter derivees de l'inventaire (hotes actifs de ce groupe). +serveurs_prometheus_groupe_metriques: "clients_metriques" +serveurs_prometheus_port_node: 9100 + +# Jobs additionnels libres : [{ job: nom, cibles: ["hote:port", ...] }] +serveurs_prometheus_cibles_supplementaires: [] diff --git a/roles/serveurs_prometheus/handlers/main.yml b/roles/serveurs_prometheus/handlers/main.yml new file mode 100644 index 0000000..8d3c56b --- /dev/null +++ b/roles/serveurs_prometheus/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Valider la configuration prometheus + ansible.builtin.command: + cmd: "promtool check config {{ serveurs_prometheus_config }}" + changed_when: false + listen: Valider et recharger prometheus + +- name: Redemarrer prometheus + ansible.builtin.systemd: + name: "{{ serveurs_prometheus_service }}" + state: restarted + listen: Valider et recharger prometheus diff --git a/roles/serveurs_prometheus/tasks/main.yml b/roles/serveurs_prometheus/tasks/main.yml new file mode 100644 index 0000000..a294c2a --- /dev/null +++ b/roles/serveurs_prometheus/tasks/main.yml @@ -0,0 +1,31 @@ +--- +- name: Installer Prometheus + ansible.builtin.apt: + name: "{{ serveurs_prometheus_paquets }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Deployer la configuration Prometheus + ansible.builtin.template: + src: prometheus.yml.j2 + dest: "{{ serveurs_prometheus_config }}" + owner: root + group: root + mode: "0644" + notify: Valider et recharger prometheus + +- name: Definir les arguments du service (retention) + ansible.builtin.template: + src: default-prometheus.j2 + dest: /etc/default/prometheus + owner: root + group: root + mode: "0644" + notify: Valider et recharger prometheus + +- name: Activer et demarrer Prometheus + ansible.builtin.systemd: + name: "{{ serveurs_prometheus_service }}" + enabled: true + state: started diff --git a/roles/serveurs_prometheus/templates/default-prometheus.j2 b/roles/serveurs_prometheus/templates/default-prometheus.j2 new file mode 100644 index 0000000..1ef72de --- /dev/null +++ b/roles/serveurs_prometheus/templates/default-prometheus.j2 @@ -0,0 +1,2 @@ +# Gere par Set-OPS (role serveurs_prometheus). +ARGS="--storage.tsdb.retention.time={{ serveurs_prometheus_retention }}" diff --git a/roles/serveurs_prometheus/templates/prometheus.yml.j2 b/roles/serveurs_prometheus/templates/prometheus.yml.j2 new file mode 100644 index 0000000..e1fdf61 --- /dev/null +++ b/roles/serveurs_prometheus/templates/prometheus.yml.j2 @@ -0,0 +1,23 @@ +# Gere par Set-OPS (role serveurs_prometheus). Ne pas editer a la main. +global: + scrape_interval: {{ serveurs_prometheus_intervalle }} + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ['localhost:9090'] + + - job_name: node + static_configs: + - targets: {{ groups.get(serveurs_prometheus_groupe_metriques, []) + | map('extract', hostvars) + | selectattr('ansible_host', 'defined') + | map(attribute='ansible_host') + | map('regex_replace', '$', ':' ~ serveurs_prometheus_port_node) + | list | to_json }} +{% for job in serveurs_prometheus_cibles_supplementaires %} + + - job_name: {{ job.job }} + static_configs: + - targets: {{ job.cibles | to_json }} +{% endfor %} diff --git a/roles/serveurs_redis/README.md b/roles/serveurs_redis/README.md new file mode 100644 index 0000000..f37bb89 --- /dev/null +++ b/roles/serveurs_redis/README.md @@ -0,0 +1,30 @@ +# serveurs_redis + +Cache / files **Redis** (paquet Debian) sur le nœud données (data-01), aux côtés de +PostgreSQL. + +## Rôle +- Installe `redis-server`. +- Déploie une **surcharge** `/etc/redis/chezlepro.conf` (incluse en fin de `redis.conf`, + donc prioritaire) : écoute, `requirepass`, `maxmemory` + politique d'éviction. +- N'écrase pas le `redis.conf` du paquet (approche non destructive par `include`). + +## Sécurité +- Exposé sur le réseau interne (`127.0.0.1` + IP interne) → **mot de passe obligatoire** + (`requirepass`, depuis Vault). `protected-mode yes`. +- L'accès reste restreint par la segmentation réseau (VLAN données / nftables). + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_redis_ecoute` | `127.0.0.1` + IP interne | Adresses d'écoute (`bind`) | +| `serveurs_redis_maxmemory` | `256mb` | Limite mémoire | +| `serveurs_redis_maxmemory_policy` | `allkeys-lru` | Éviction (cache) | +| `serveurs_redis_password` | `""` (Vault requis) | `requirepass` | + +## Intégration +- Consommé par les applis qui ont besoin d'un cache (Nextcloud, etc.) via leur chaîne + de connexion Redis (hôte data-01, port 6379, mot de passe). + +## Hors périmètre +- Réplication / Sentinel / Cluster, persistance avancée (AOF tuning), TLS. diff --git a/roles/serveurs_redis/defaults/main.yml b/roles/serveurs_redis/defaults/main.yml new file mode 100644 index 0000000..fe0072b --- /dev/null +++ b/roles/serveurs_redis/defaults/main.yml @@ -0,0 +1,18 @@ +--- +serveurs_redis_paquets: + - redis-server + +serveurs_redis_service: "redis-server" +serveurs_redis_config_principal: "/etc/redis/redis.conf" +serveurs_redis_config: "/etc/redis/chezlepro.conf" + +serveurs_redis_port: 6379 +serveurs_redis_ecoute: + - "127.0.0.1" + - "{{ ansible_host | default(ansible_default_ipv4.address, true) }}" + +serveurs_redis_maxmemory: "256mb" +serveurs_redis_maxmemory_policy: "allkeys-lru" + +# Mot de passe OBLIGATOIRE (expose sur le reseau interne). Ansible Vault. +serveurs_redis_password: "" # {{ vault_redis }} diff --git a/roles/serveurs_redis/handlers/main.yml b/roles/serveurs_redis/handlers/main.yml new file mode 100644 index 0000000..188525f --- /dev/null +++ b/roles/serveurs_redis/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Redemarrer redis + ansible.builtin.systemd: + name: "{{ serveurs_redis_service }}" + state: restarted + listen: Redemarrer redis diff --git a/roles/serveurs_redis/tasks/main.yml b/roles/serveurs_redis/tasks/main.yml new file mode 100644 index 0000000..eed0877 --- /dev/null +++ b/roles/serveurs_redis/tasks/main.yml @@ -0,0 +1,37 @@ +--- +- name: Exiger le mot de passe Redis (Vault) + ansible.builtin.assert: + that: + - serveurs_redis_password | length > 0 + fail_msg: "serveurs_redis_password est requis (Redis est expose sur le reseau interne)." + +- name: Installer Redis + ansible.builtin.apt: + name: "{{ serveurs_redis_paquets }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Deployer la surcharge de configuration Chezlepro + ansible.builtin.template: + src: chezlepro.conf.j2 + dest: "{{ serveurs_redis_config }}" + owner: redis + group: redis + mode: "0640" + no_log: true + notify: Redemarrer redis + +- name: Inclure la surcharge depuis redis.conf + ansible.builtin.lineinfile: + path: "{{ serveurs_redis_config_principal }}" + line: "include {{ serveurs_redis_config }}" + insertafter: EOF + state: present + notify: Redemarrer redis + +- name: Activer et demarrer Redis + ansible.builtin.systemd: + name: "{{ serveurs_redis_service }}" + enabled: true + state: started diff --git a/roles/serveurs_redis/templates/chezlepro.conf.j2 b/roles/serveurs_redis/templates/chezlepro.conf.j2 new file mode 100644 index 0000000..92a0e55 --- /dev/null +++ b/roles/serveurs_redis/templates/chezlepro.conf.j2 @@ -0,0 +1,7 @@ +# Gere par Set-OPS (role serveurs_redis). Surcharge /etc/redis/redis.conf. +bind {{ serveurs_redis_ecoute | join(' ') }} +port {{ serveurs_redis_port }} +protected-mode yes +requirepass {{ serveurs_redis_password }} +maxmemory {{ serveurs_redis_maxmemory }} +maxmemory-policy {{ serveurs_redis_maxmemory_policy }} diff --git a/roles/serveurs_sendmail/README.md b/roles/serveurs_sendmail/README.md new file mode 100644 index 0000000..2820c1b --- /dev/null +++ b/roles/serveurs_sendmail/README.md @@ -0,0 +1,37 @@ +# serveurs_sendmail + +Relais SMTP sortant interne. **Implémenté avec Postfix** (fournit l'interface +`sendmail`-compatible `/usr/sbin/sendmail` qu'utilisent les clients). + +## Rôle +- Préconfigure Postfix via debconf (installation non interactive, « Internet Site »). +- Installe `postfix` et `ssl-cert`. +- Déploie `/etc/postfix/main.cf` : relais sortant uniquement, **n'accepte de relayer que pour `mynetworks`** (réseaux internes). +- **Valide la configuration (`postfix check`) avant de recharger** (handler `Valider et recharger postfix`). + +## Sécurité +- `smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination` → pas de relais ouvert. +- `mynetworks` par défaut : boucle locale + `10.1.0.0/16`. +- N'héberge pas de boîtes (`mydestination` ne contient pas le domaine) : remise relayée. +- TLS opportuniste (`may`), certificat snakeoil par défaut. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_sendmail_domaine` | `chezlepro.internal` | `mydomain` / mailname | +| `serveurs_sendmail_reseaux_autorises` | boucle + `10.1.0.0/16` | `mynetworks` (qui peut relayer) | +| `serveurs_sendmail_smarthost` | `""` | Relais amont (vide = remise directe MX) | +| `serveurs_sendmail_inet_interfaces` | `all` | Interfaces d'écoute | +| `serveurs_sendmail_tls_cert` / `_cle` | snakeoil | Certificat TLS | + +## Smarthost amont +```yaml +serveurs_sendmail_smarthost: "[smtp.exemple.com]:587" +``` +(L'authentification SASL vers un smarthost externe, si requise, viendra avec ses +identifiants depuis Ansible Vault — hors périmètre du socle actuel.) + +## Hors périmètre +- Réception/hébergement de courrier (boîtes, IMAP). +- Authentification SASL sortante vers smarthost (à ajouter avec Vault si besoin). +- DKIM/SPF/DMARC signature (phase dédiée). diff --git a/roles/serveurs_sendmail/defaults/main.yml b/roles/serveurs_sendmail/defaults/main.yml new file mode 100644 index 0000000..97beceb --- /dev/null +++ b/roles/serveurs_sendmail/defaults/main.yml @@ -0,0 +1,31 @@ +--- +# Relais SMTP sortant base sur Postfix (fournit l'interface sendmail-compatible). +serveurs_sendmail_packages: + - postfix + - ssl-cert + +serveurs_sendmail_service_name: "postfix" + +# Identite. +serveurs_sendmail_domaine: "chezlepro.internal" +serveurs_sendmail_origine: "$mydomain" +serveurs_sendmail_nom_hote: "{{ ansible_fqdn | default(ansible_hostname) }}" + +# Reseaux internes autorises a relayer (clients_smtp). +serveurs_sendmail_reseaux_autorises: + - "127.0.0.0/8" + - "[::1]/128" + - "10.1.0.0/16" + +# Smarthost amont optionnel. Vide => remise directe (MX). +serveurs_sendmail_smarthost: "" # ex. "[smtp.exemple.com]:587" + +# Relais uniquement : ne pas heberger de boites pour le domaine. +serveurs_sendmail_destinations_locales: "$myhostname, localhost.$mydomain, localhost" + +# Interfaces d'ecoute. +serveurs_sendmail_inet_interfaces: "all" + +# TLS (snakeoil par defaut ; bascule vers l'AC interne plus tard). +serveurs_sendmail_tls_cert: "/etc/ssl/certs/ssl-cert-snakeoil.pem" +serveurs_sendmail_tls_cle: "/etc/ssl/private/ssl-cert-snakeoil.key" diff --git a/roles/serveurs_sendmail/handlers/main.yml b/roles/serveurs_sendmail/handlers/main.yml new file mode 100644 index 0000000..65319e8 --- /dev/null +++ b/roles/serveurs_sendmail/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Verifier la configuration Postfix + ansible.builtin.command: + cmd: postfix check + changed_when: false + listen: Valider et recharger postfix + +- name: Recharger postfix + ansible.builtin.systemd: + name: "{{ serveurs_sendmail_service_name }}" + state: reloaded + listen: Valider et recharger postfix diff --git a/roles/serveurs_sendmail/tasks/main.yml b/roles/serveurs_sendmail/tasks/main.yml new file mode 100644 index 0000000..113aa0a --- /dev/null +++ b/roles/serveurs_sendmail/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Preconfigurer Postfix (debconf, installation non interactive) + ansible.builtin.debconf: + name: postfix + question: "{{ item.question }}" + vtype: "{{ item.vtype }}" + value: "{{ item.value }}" + loop: + - { question: "postfix/main_mailer_type", vtype: "select", value: "Internet Site" } + - { question: "postfix/mailname", vtype: "string", value: "{{ serveurs_sendmail_domaine }}" } + loop_control: + label: "{{ item.question }}" + +- name: Installer Postfix + ansible.builtin.apt: + name: "{{ serveurs_sendmail_packages }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Deployer la configuration Postfix + ansible.builtin.template: + src: main.cf.j2 + dest: /etc/postfix/main.cf + owner: root + group: root + mode: "0644" + notify: Valider et recharger postfix + +- name: Activer et demarrer Postfix + ansible.builtin.systemd: + name: "{{ serveurs_sendmail_service_name }}" + enabled: true + state: started diff --git a/roles/serveurs_sendmail/templates/main.cf.j2 b/roles/serveurs_sendmail/templates/main.cf.j2 new file mode 100644 index 0000000..dc3a2c2 --- /dev/null +++ b/roles/serveurs_sendmail/templates/main.cf.j2 @@ -0,0 +1,31 @@ +# Gere par Set-OPS (role serveurs_sendmail, base Postfix). Ne pas editer a la main. +smtpd_banner = $myhostname ESMTP +biff = no +append_dot_mydomain = no +readme_directory = no +compatibility_level = 3.6 + +myhostname = {{ serveurs_sendmail_nom_hote }} +mydomain = {{ serveurs_sendmail_domaine }} +myorigin = {{ serveurs_sendmail_origine }} +mydestination = {{ serveurs_sendmail_destinations_locales }} +mynetworks = {{ serveurs_sendmail_reseaux_autorises | join(', ') }} +relayhost = {{ serveurs_sendmail_smarthost }} +inet_interfaces = {{ serveurs_sendmail_inet_interfaces }} +inet_protocols = all + +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases +mailbox_size_limit = 0 +recipient_delimiter = + + +# Relais limite aux reseaux internes. +smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination + +# TLS opportuniste. +smtpd_tls_cert_file = {{ serveurs_sendmail_tls_cert }} +smtpd_tls_key_file = {{ serveurs_sendmail_tls_cle }} +smtpd_tls_security_level = may +smtp_tls_security_level = may +smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache diff --git a/roles/serveurs_step_ca/README.md b/roles/serveurs_step_ca/README.md new file mode 100644 index 0000000..681de84 --- /dev/null +++ b/roles/serveurs_step_ca/README.md @@ -0,0 +1,41 @@ +# serveurs_step_ca + +Autorité de certification interne **Smallstep step-ca** avec provisioner **ACME**, +pour émettre les certificats internes de Chezlepro (`clients_pki` est le client ACME). + +## Rôle +- Ajoute le **dépôt apt officiel Smallstep** (clé + source deb822) et installe `step-ca` + `step-cli`. +- Crée l'utilisateur système `step` et `STEPPATH=/etc/step-ca`. +- Déploie les mots de passe (CA et provisioner) depuis **Vault**. +- **Initialise l'AC une seule fois** (`step ca init`, standalone + provisioner ACME), garde-fou `creates: config/ca.json`. +- Déploie l'**unité systemd durcie** officielle et démarre le service. + +## Secrets requis (Vault) +Obligatoires dès l'installation : + +```yaml +serveurs_step_ca_password: "{{ vault_step_ca_password }}" +serveurs_step_ca_provisioner_password: "{{ vault_step_ca_provisioner_password }}" +``` + +À placer dans `inventories/production/group_vars/serveurs_step_ca/vault.yml` (chiffré), +les variables en clair référençant ces secrets dans `.../serveurs_step_ca/main.yml`. + +## Variables principales +| Variable | Défaut | Rôle | +| --- | --- | --- | +| `serveurs_step_ca_nom` | `Chezlepro Internal CA` | Nom de l'AC | +| `serveurs_step_ca_dns` | FQDN + IP | SAN/DNS du certificat de l'AC | +| `serveurs_step_ca_adresse` | `:8443` | Adresse d'écoute | +| `serveurs_step_ca_provisioner` | `admin@chezlepro.internal` | Provisioner JWK admin | +| `serveurs_step_ca_acme` | `true` | Ajoute un provisioner ACME | + +## Notes +- La ligne `step ca init` utilise les drapeaux documentés par Smallstep ; selon la + version de `step-cli` installée, un ajustement mineur peut être nécessaire. L'init + ne s'exécute qu'une fois (présence de `config/ca.json`). +- L'unité systemd provient de la doc de production Smallstep (sandboxing systemd). +- Émission/renouvellement côté clients : voir le futur rôle `clients_pki` (ACME). + +## Hors périmètre +- Sauvegarde/HA de l'AC, RA (registration authority), rotation des clés racine. diff --git a/roles/serveurs_step_ca/defaults/main.yml b/roles/serveurs_step_ca/defaults/main.yml new file mode 100644 index 0000000..787a2e9 --- /dev/null +++ b/roles/serveurs_step_ca/defaults/main.yml @@ -0,0 +1,27 @@ +--- +# Autorite de certification interne (Smallstep step-ca) + ACME. +serveurs_step_ca_paquets: + - step-cli + - step-ca + +# Depot apt officiel Smallstep. +serveurs_step_ca_depot_cle_url: "https://packages.smallstep.com/keys/apt/repo-signing-key.gpg" +serveurs_step_ca_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc" +serveurs_step_ca_depot_uri: "https://packages.smallstep.com/stable/debian" +serveurs_step_ca_depot_suite: "debs" + +# Service. +serveurs_step_ca_utilisateur: "step" +serveurs_step_ca_steppath: "/etc/step-ca" +serveurs_step_ca_service: "step-ca" + +# Identite de l'AC. +serveurs_step_ca_nom: "Chezlepro Internal CA" +serveurs_step_ca_dns: "{{ ansible_fqdn | default(ansible_hostname) }},{{ ansible_host | default('127.0.0.1') }}" +serveurs_step_ca_adresse: ":8443" +serveurs_step_ca_provisioner: "admin@chezlepro.internal" +serveurs_step_ca_acme: true + +# Secrets (Ansible Vault) — obligatoires pour l'initialisation. +serveurs_step_ca_password: "" # {{ vault_step_ca_password }} +serveurs_step_ca_provisioner_password: "" # {{ vault_step_ca_provisioner_password }} diff --git a/roles/serveurs_step_ca/handlers/main.yml b/roles/serveurs_step_ca/handlers/main.yml new file mode 100644 index 0000000..2dbd286 --- /dev/null +++ b/roles/serveurs_step_ca/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Redemarrer step-ca + ansible.builtin.systemd: + name: "{{ serveurs_step_ca_service }}" + state: restarted + daemon_reload: true + listen: Redemarrer step-ca diff --git a/roles/serveurs_step_ca/tasks/main.yml b/roles/serveurs_step_ca/tasks/main.yml new file mode 100644 index 0000000..f57f797 --- /dev/null +++ b/roles/serveurs_step_ca/tasks/main.yml @@ -0,0 +1,108 @@ +--- +- name: Exiger les secrets de l'AC (Vault) + ansible.builtin.assert: + that: + - serveurs_step_ca_password | length > 0 + - serveurs_step_ca_provisioner_password | length > 0 + fail_msg: >- + serveurs_step_ca_password et serveurs_step_ca_provisioner_password + sont requis (a fournir via Ansible Vault). + +- name: Assurer le repertoire des trousseaux apt + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + +- name: Telecharger la cle de signature Smallstep + ansible.builtin.get_url: + url: "{{ serveurs_step_ca_depot_cle_url }}" + dest: "{{ serveurs_step_ca_depot_cle_fichier }}" + owner: root + group: root + mode: "0644" + +- name: Ajouter le depot apt Smallstep + ansible.builtin.template: + src: smallstep.sources.j2 + dest: /etc/apt/sources.list.d/smallstep.sources + owner: root + group: root + mode: "0644" + +- name: Installer step-ca et step-cli + ansible.builtin.apt: + name: "{{ serveurs_step_ca_paquets }}" + state: present + update_cache: true + +- name: Creer l'utilisateur systeme step + ansible.builtin.user: + name: "{{ serveurs_step_ca_utilisateur }}" + system: true + home: "{{ serveurs_step_ca_steppath }}" + create_home: false + shell: /usr/sbin/nologin + +- name: Creer le repertoire STEPPATH + ansible.builtin.file: + path: "{{ serveurs_step_ca_steppath }}" + state: directory + owner: "{{ serveurs_step_ca_utilisateur }}" + group: "{{ serveurs_step_ca_utilisateur }}" + mode: "0700" + +- name: Deployer le mot de passe de l'AC + ansible.builtin.copy: + content: "{{ serveurs_step_ca_password }}" + dest: "{{ serveurs_step_ca_steppath }}/password.txt" + owner: "{{ serveurs_step_ca_utilisateur }}" + group: "{{ serveurs_step_ca_utilisateur }}" + mode: "0600" + no_log: true + notify: Redemarrer step-ca + +- name: Deployer le mot de passe du provisioner + ansible.builtin.copy: + content: "{{ serveurs_step_ca_provisioner_password }}" + dest: "{{ serveurs_step_ca_steppath }}/provisioner_password.txt" + owner: "{{ serveurs_step_ca_utilisateur }}" + group: "{{ serveurs_step_ca_utilisateur }}" + mode: "0600" + no_log: true + +- name: Initialiser l'autorite de certification (une seule fois) + ansible.builtin.command: + cmd: >- + step ca init + --deployment-type standalone + --name "{{ serveurs_step_ca_nom }}" + --dns "{{ serveurs_step_ca_dns }}" + --address "{{ serveurs_step_ca_adresse }}" + --provisioner "{{ serveurs_step_ca_provisioner }}" + --password-file {{ serveurs_step_ca_steppath }}/password.txt + --provisioner-password-file {{ serveurs_step_ca_steppath }}/provisioner_password.txt + {{ '--acme' if serveurs_step_ca_acme | bool else '' }} + creates: "{{ serveurs_step_ca_steppath }}/config/ca.json" + become: true + become_user: "{{ serveurs_step_ca_utilisateur }}" + environment: + STEPPATH: "{{ serveurs_step_ca_steppath }}" + +- name: Deployer l'unite systemd step-ca + ansible.builtin.template: + src: step-ca.service.j2 + dest: /etc/systemd/system/step-ca.service + owner: root + group: root + mode: "0644" + notify: Redemarrer step-ca + +- name: Activer et demarrer step-ca + ansible.builtin.systemd: + name: "{{ serveurs_step_ca_service }}" + enabled: true + state: started + daemon_reload: true diff --git a/roles/serveurs_step_ca/templates/smallstep.sources.j2 b/roles/serveurs_step_ca/templates/smallstep.sources.j2 new file mode 100644 index 0000000..438d7da --- /dev/null +++ b/roles/serveurs_step_ca/templates/smallstep.sources.j2 @@ -0,0 +1,5 @@ +Types: deb +URIs: {{ serveurs_step_ca_depot_uri }} +Suites: {{ serveurs_step_ca_depot_suite }} +Components: main +Signed-By: {{ serveurs_step_ca_depot_cle_fichier }} diff --git a/roles/serveurs_step_ca/templates/step-ca.service.j2 b/roles/serveurs_step_ca/templates/step-ca.service.j2 new file mode 100644 index 0000000..50307eb --- /dev/null +++ b/roles/serveurs_step_ca/templates/step-ca.service.j2 @@ -0,0 +1,52 @@ +[Unit] +Description=step-ca service +Documentation=https://smallstep.com/docs/step-ca +Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=30 +StartLimitBurst=3 +ConditionFileNotEmpty={{ serveurs_step_ca_steppath }}/config/ca.json +ConditionFileNotEmpty={{ serveurs_step_ca_steppath }}/password.txt + +[Service] +Type=simple +User={{ serveurs_step_ca_utilisateur }} +Group={{ serveurs_step_ca_utilisateur }} +Environment=STEPPATH={{ serveurs_step_ca_steppath }} +WorkingDirectory={{ serveurs_step_ca_steppath }} +ExecStart=/usr/bin/step-ca config/ca.json --password-file password.txt +ExecReload=/bin/kill --signal HUP $MAINPID +Restart=on-failure +RestartSec=5 +TimeoutStopSec=30 + +; Process capabilities & privileges +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +SecureBits=keep-caps +NoNewPrivileges=yes + +; Sandboxing +ProtectSystem=full +ProtectHome=true +RestrictNamespaces=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +PrivateTmp=true +PrivateDevices=true +ProtectClock=true +ProtectControlGroups=true +ProtectKernelTunables=true +ProtectKernelLogs=true +ProtectKernelModules=true +LockPersonality=true +RestrictSUIDSGID=true +RemoveIPC=true +RestrictRealtime=true +SystemCallFilter=@system-service +SystemCallArchitectures=native +MemoryDenyWriteExecute=true +ReadWriteDirectories={{ serveurs_step_ca_steppath }}/db + +[Install] +WantedBy=multi-user.target diff --git a/roles/web/README.md b/roles/web/README.md index ef12510..5e19b10 100644 --- a/roles/web/README.md +++ b/roles/web/README.md @@ -1,3 +1,8 @@ # web -Rôles web : reverse proxy, NGINX, sites statiques. +Couche applicative web Chezlepro, distincte de l'edge `serveurs_nginx`. + +- frontaux : présentation web (UI, rendu, assets), groupe `serveurs_web_frontaux` ; +- dorsaux : application web (API, traitement), groupe `serveurs_web_dorsaux`. + +Le mandataire inverse, la terminaison TLS et le WAF relèvent de `serveurs_nginx`, pas de ce rôle. diff --git a/scripts/inventory_gui.py b/scripts/inventory_gui.py index 0a902da..112a82a 100644 --- a/scripts/inventory_gui.py +++ b/scripts/inventory_gui.py @@ -6,6 +6,12 @@ from __future__ import annotations from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import argparse import json +import os +import re +import secrets +import subprocess +import tempfile +import threading from pathlib import Path from urllib.parse import urlparse @@ -16,14 +22,42 @@ from inventory_rules import ( GROUPE_HOTES_PLANIFIES, GROUPES_ETAT_HOTE, charger_dependances, + charger_nomenclature, est_groupe_operationnel, groupes_operationnels_connus, ) RACINE = Path(__file__).resolve().parents[1] INVENTAIRE_DEFAUT = RACINE / "inventories/production/hosts.yml" +INVENTAIRE_PRODUCTION = (RACINE / "inventories/production/hosts.yml").resolve() DOSSIER_PLAYBOOKS_GROUPES = RACINE / "playbooks/groupes" FICHIER_DEPENDANCES = RACINE / "docs/dependances-groupes.yml" +FICHIER_NOMENCLATURE = RACINE / "docs/nomenclature.yml" + +# Garde-fous des executions (verifier / deployer) depuis l'interface. +JETON = secrets.token_urlsafe(18) +VERROU = threading.Lock() +VERIF_OK: dict[str, bool] = {} +MOTIF_HOTE = re.compile(r"^[A-Za-z0-9._-]{1,63}$") + + +# Champs de provisioning persistes comme variables d'hote dans l'inventaire. +# (cle_gui, variable_inventaire, type) +CHAMPS_PROVISION = [ + ("adresse_ip", "ansible_host", "str"), + ("utilisateur_ansible", "ansible_user", "str"), + ("cidr", "proxmox_cidr", "int"), + ("passerelle", "proxmox_passerelle", "str"), + ("vlan", "proxmox_vlan", "int"), + ("pont", "proxmox_pont", "str"), + ("dns", "proxmox_dns", "str"), + ("vmid", "proxmox_vmid", "int"), + ("noeud", "proxmox_noeud", "str"), + ("stockage", "proxmox_stockage", "str"), + ("disque_taille", "proxmox_disque_taille", "str"), + ("memoire", "proxmox_memoire", "int"), + ("coeurs", "proxmox_coeurs", "int"), +] def charger_yaml(path: Path) -> dict: @@ -51,6 +85,36 @@ def groupes_disponibles(data: dict) -> list[str]: return groupes_operationnels_connus(enfants(data), DOSSIER_PLAYBOOKS_GROUPES) +def roles_des_groupes() -> dict: + """Pour chaque playbook de groupe, extrait les roles appliques (chaine groupe -> roles).""" + resultat: dict = {} + for chemin in sorted(DOSSIER_PLAYBOOKS_GROUPES.glob("*.yml")): + roles: list[str] = [] + try: + with chemin.open("r", encoding="utf-8") as fichier: + docs = yaml.safe_load(fichier) or [] + plays = docs if isinstance(docs, list) else [docs] + for play in plays: + if not isinstance(play, dict): + continue + for role in play.get("roles", []) or []: + if isinstance(role, str): + roles.append(role) + elif isinstance(role, dict): + nom = role.get("role") or role.get("name") + if nom: + roles.append(str(nom)) + except (OSError, yaml.YAMLError): + pass + resultat[chemin.stem] = {"roles": roles, "stub": not roles} + return resultat + + +def hotes_actifs_fichier(path: Path) -> set[str]: + data = charger_yaml(path) + return set(enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {}) or {}) + + def variables_hote(data: dict, hote: str) -> dict: resultat: dict = {} for groupe_data in enfants(data).values(): @@ -86,16 +150,13 @@ def liste_hotes(data: dict) -> list[dict]: if not visible: continue variables = variables_hote(data, nom) - hotes.append( - { - "nom": nom, - "etat": etat, - "adresse_ip": variables.get("ansible_host", ""), - "utilisateur_ansible": variables.get("ansible_user", "ansible"), - "vmid": variables.get("proxmox_vmid", ""), - "groupes": sorted(groupes), - } - ) + hote = {"nom": nom, "etat": etat, "groupes": sorted(groupes)} + for cle, var, _type in CHAMPS_PROVISION: + valeur = variables.get(var, "") + hote[cle] = "" if valeur is None else valeur + if not str(hote.get("utilisateur_ansible", "")).strip(): + hote["utilisateur_ansible"] = "ansible" + hotes.append(hote) return hotes @@ -104,23 +165,25 @@ def inventaire_api(path: Path) -> dict: dependencies = charger_dependances(FICHIER_DEPENDANCES) return { "inventaire": str(path.relative_to(RACINE)), + "production": path.resolve() == INVENTAIRE_PRODUCTION, "groupes": groupes_disponibles(data), "dependances": dependencies, + "nomenclature": charger_nomenclature(FICHIER_NOMENCLATURE), + "chaine": roles_des_groupes(), "hotes": liste_hotes(data), } def donnees_hote(hote: dict) -> dict: variables: dict = {} - adresse_ip = str(hote.get("adresse_ip", "")).strip() - utilisateur = str(hote.get("utilisateur_ansible", "")).strip() - vmid = str(hote.get("vmid", "")).strip() - if adresse_ip: - variables["ansible_host"] = adresse_ip - if utilisateur: - variables["ansible_user"] = utilisateur - if vmid: - variables["proxmox_vmid"] = int(vmid) if vmid.isdigit() else vmid + for cle, var, typ in CHAMPS_PROVISION: + brut = str(hote.get(cle, "")).strip() + if not brut: + continue + if typ == "int" and brut.lstrip("-").isdigit(): + variables[var] = int(brut) + else: + variables[var] = brut return variables @@ -190,6 +253,18 @@ def valider_payload(payload: dict, groupes_connus: list[str], dependencies: dict inconnus = sorted(set(groupes) - set(groupes_connus)) if inconnus: raise ValueError(f"Groupes inconnus pour {nom}: {', '.join(inconnus)}") + + vlan = str(hote.get("vlan", "")).strip() + if vlan and (not vlan.isdigit() or not 1 <= int(vlan) <= 4094): + raise ValueError(f"VLAN invalide pour {nom}: {vlan} (attendu 1-4094)") + cidr = str(hote.get("cidr", "")).strip() + if cidr and (not cidr.isdigit() or not 0 <= int(cidr) <= 32): + raise ValueError(f"CIDR invalide pour {nom}: {cidr} (attendu 0-32)") + for cle, libelle in (("memoire", "Memoire"), ("coeurs", "Coeurs")): + valeur = str(hote.get(cle, "")).strip() + if valeur and (not valeur.isdigit() or int(valeur) <= 0): + raise ValueError(f"{libelle} invalide pour {nom}: {valeur} (entier positif attendu)") + vmid = str(hote.get("vmid", "")).strip() if not vmid: continue @@ -205,122 +280,315 @@ HTML = r""" - Set-OPS - Inventaire + Set-OPS · Votre artisan numérique
-
-

Inventaire Set-OPS

-
-
-
+

Set-OPS

Votre artisan numérique
+ +
+
- - - +
+ + +
+ + + +
-
-
+
+
+
+ Dépendances causales +
+
+
+ + @@ -545,21 +1085,94 @@ HTML = r""" class Gestionnaire(BaseHTTPRequestHandler): inventaire: Path = INVENTAIRE_DEFAUT + def log_message(self, *args) -> None: # silence par defaut + pass + def repondre(self, status: int, contenu: bytes, content_type: str) -> None: - self.send_response(status) - self.send_header("Content-Type", content_type) - self.send_header("Content-Length", str(len(contenu))) - self.end_headers() - self.wfile.write(contenu) + try: + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", str(len(contenu))) + self.end_headers() + self.wfile.write(contenu) + except (BrokenPipeError, ConnectionResetError): + pass # le client a ferme la connexion : rien a faire def repondre_json(self, status: int, data: dict) -> None: self.repondre(status, json.dumps(data, ensure_ascii=False).encode("utf-8"), "application/json; charset=utf-8") + def _ecrire_flux(self, texte: str) -> bool: + try: + self.wfile.write(texte.encode("utf-8", "replace")) + self.wfile.flush() + return True + except (BrokenPipeError, ConnectionResetError): + return False + + def executer_flux(self, hote: str, mode: str, vault: str | None = None) -> None: + if not MOTIF_HOTE.match(hote or ""): + self.repondre_json(400, {"erreur": "Nom d'hote invalide."}) + return + if self.inventaire != INVENTAIRE_PRODUCTION: + self.repondre_json(409, {"erreur": "Deploiement reserve a l'inventaire production."}) + return + if hote not in hotes_actifs_fichier(self.inventaire): + self.repondre_json(409, {"erreur": f"{hote} n'est pas un hote actif sauvegarde."}) + return + if mode == "deployer" and not VERIF_OK.get(hote): + self.repondre_json(409, {"erreur": "Verification (dry-run) requise avant de deployer."}) + return + if not VERROU.acquire(blocking=False): + self.repondre_json(409, {"erreur": "Une execution est deja en cours."}) + return + fichier_vault = None + try: + cible = "deployer" if mode == "deployer" else "verifier-deploiement" + env = dict(os.environ, PYTHONUNBUFFERED="1", ANSIBLE_FORCE_COLOR="0") + if vault: + fd, fichier_vault = tempfile.mkstemp(prefix="setops-vault-") # 0600 par defaut + os.write(fd, str(vault).encode("utf-8")) + os.close(fd) + env["ANSIBLE_VAULT_PASSWORD_FILE"] = fichier_vault + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Cache-Control", "no-cache") + self.end_headers() + self._ecrire_flux(f"$ make {cible} HOTE={hote}\n\n") + proc = subprocess.Popen( + ["make", cible, f"HOTE={hote}"], + cwd=str(RACINE), env=env, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, + ) + try: + for ligne in proc.stdout: + if not self._ecrire_flux(ligne): + proc.terminate() + break + finally: + rc = proc.wait() + if mode == "verifier" and rc == 0: + VERIF_OK[hote] = True + elif mode == "deployer": + VERIF_OK.pop(hote, None) + self._ecrire_flux(f"\n__FIN__ rc={rc}\n") + finally: + if fichier_vault: + try: + os.remove(fichier_vault) + except OSError: + pass + VERROU.release() + def do_GET(self) -> None: chemin = urlparse(self.path).path try: if chemin == "/": - self.repondre(200, HTML.encode("utf-8"), "text/html; charset=utf-8") + page = HTML.replace("__JETON__", JETON) + self.repondre(200, page.encode("utf-8"), "text/html; charset=utf-8") + elif chemin == "/favicon.ico": + self.repondre(204, b"", "image/x-icon") elif chemin == "/api/inventaire": self.repondre_json(200, inventaire_api(self.inventaire)) else: @@ -569,19 +1182,32 @@ class Gestionnaire(BaseHTTPRequestHandler): def do_POST(self) -> None: chemin = urlparse(self.path).path + if self.headers.get("X-Jeton") != JETON: + self.repondre_json(403, {"erreur": "Jeton manquant ou invalide."}) + return + longueur = int(self.headers.get("Content-Length", "0") or 0) + corps = self.rfile.read(longueur).decode("utf-8") if longueur else "" try: - if chemin != "/api/inventaire": + donnees = json.loads(corps) if corps else {} + except json.JSONDecodeError: + self.repondre_json(400, {"erreur": "Corps JSON invalide."}) + return + try: + if chemin == "/api/inventaire": + courant = charger_yaml(self.inventaire) + groupes_connus = groupes_disponibles(courant) + dependencies = charger_dependances(FICHIER_DEPENDANCES) + valider_payload(donnees, groupes_connus, dependencies) + nouveau = construire_inventaire(donnees, groupes_connus) + ecrire_yaml(self.inventaire, nouveau) + VERIF_OK.clear() + self.repondre_json(200, inventaire_api(self.inventaire)) + elif chemin == "/api/verifier": + self.executer_flux(str(donnees.get("hote", "")), "verifier", donnees.get("vault")) + elif chemin == "/api/deployer": + self.executer_flux(str(donnees.get("hote", "")), "deployer", donnees.get("vault")) + else: self.repondre_json(404, {"erreur": "Introuvable"}) - return - longueur = int(self.headers.get("Content-Length", "0")) - payload = json.loads(self.rfile.read(longueur).decode("utf-8")) - courant = charger_yaml(self.inventaire) - groupes_connus = groupes_disponibles(courant) - dependencies = charger_dependances(FICHIER_DEPENDANCES) - valider_payload(payload, groupes_connus, dependencies) - nouveau = construire_inventaire(payload, groupes_connus) - ecrire_yaml(self.inventaire, nouveau) - self.repondre_json(200, inventaire_api(self.inventaire)) except Exception as exc: self.repondre_json(400, {"erreur": str(exc)}) diff --git a/scripts/inventory_rules.py b/scripts/inventory_rules.py index 056b0eb..042b691 100644 --- a/scripts/inventory_rules.py +++ b/scripts/inventory_rules.py @@ -24,6 +24,17 @@ def groupes_operationnels_connus(enfants: dict, dossier_playbooks: Path) -> list return sorted(groupe for groupe in groupes if est_groupe_operationnel(groupe)) +def charger_nomenclature(path: Path | None) -> dict: + """Charge le registre de nomenclature (domaines, categories, adressage).""" + if not path or not path.exists(): + return {} + with path.open("r", encoding="utf-8") as fichier: + data = yaml.safe_load(fichier) or {} + if not isinstance(data, dict): + raise ValueError(f"{path} ne contient pas une table YAML.") + return data + + def charger_dependances(path: Path | None) -> dict: if not path: return {}