Structurer l'exploitation Ansible Chezlepro

This commit is contained in:
Daniel Allaire 2026-06-21 15:13:45 -04:00
parent 8fac83bcdb
commit 61e2c7ca67
92 changed files with 2829 additions and 853 deletions

2
.gitignore vendored
View file

@ -1,7 +1,9 @@
*.retry
.vault-pass
facts_cache/
.ansible/
*.vault
*.vault.yml
*.secret
*.pem
*.key

241
AGENTS.md
View file

@ -114,7 +114,7 @@ Avant de proposer un changement comme terminé, vérifier au minimum la syntaxe
Exemple pour le template Debian 13 Proxmox :
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_prepare.yml --syntax-check
ansible-playbook -i inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
```
Pour un autre playbook, remplacer le chemin par le playbook concerné.
@ -210,13 +210,12 @@ Les playbooks peuvent être classés par domaine :
```text
playbooks/
├── baseline/
├── hardening/
├── groupes/
├── maintenance/
├── monitoring/
├── networking/
├── proxmox/
├── vm_templates/
├── modeles_vm/
├── web/
├── database/
├── identity/
@ -228,6 +227,207 @@ Les rôles peuvent être ajoutés progressivement selon les besoins.
Ne pas créer de structure inutile uniquement pour donner une impression de complétude.
La conformité normale des VM déployées doit passer par `playbooks/groupes/`.
Ne pas maintenir en parallèle des playbooks de couches génériques comme `playbooks/socle/` ou `playbooks/durcissement/` lorsqu'un groupe opérationnel exprime déjà cet état voulu.
---
## Interface opérateur Makefile
Le `Makefile` est l'interface opérateur privilégiée pour les gestes courants.
Les commandes `make` doivent simplifier l'exploitation Ansible sans masquer les playbooks réellement exécutés.
Préférer quelques cibles claires et utiles :
- validation du dépôt ;
- inspection des inventaires ;
- ajout ou mise à jour d'un hôte dans un inventaire ;
- association d'un hôte à ses groupes ;
- déploiement ou remise en conformité d'un hôte ;
- déploiement ou remise en conformité d'un groupe ;
- préparation, vérification et nettoyage protégé d'un modèle de VM.
Ne pas multiplier les cibles `make` secondaires si elles ne correspondent pas à un geste réel d'exploitation.
Les cibles d'exploitation des VM doivent privilégier les groupes :
```text
make deployer HOTE=web-01
make deployer-groupe GROUPE=serveurs_debian
make appliquer GROUPE=serveurs_debian
```
Éviter les cibles parallèles qui réappliquent les mêmes rôles par couche, par exemple `make socle`, `make durcissement`, `make converger` ou `make deployer-vm`.
Toute cible `make` qui lance une action destructive ou risquée doit exiger une confirmation explicite.
---
## Convention groupes et playbooks
Chaque groupe opérationnel Ansible doit avoir un playbook homonyme dans `playbooks/groupes/`.
La convention attendue est :
```text
groupe Ansible : serveurs_debian
playbook : playbooks/groupes/serveurs_debian.yml
```
L'appartenance aux groupes détermine les services, intégrations et politiques appliqués à une VM.
`playbooks/groupes/` est la source officielle de conformité pour les VM déployées.
Un playbook de groupe doit cibler son groupe homonyme, pas `all`, sauf justification explicite.
Les concepts comme socle Debian ou durcissement commun doivent être représentés par des groupes explicites :
```text
serveurs_debian -> socle commun Debian
serveurs_durcis -> durcissement commun
clients_dns -> intégration cliente DNS
clients_pki -> intégration cliente PKI / ACME
```
Ne pas dupliquer ces mêmes rôles dans des playbooks de couches séparés.
Quand un nouveau groupe opérationnel est ajouté :
- créer le playbook homonyme dans `playbooks/groupes/` ;
- documenter son intention opérationnelle ;
- prévoir les rôles nécessaires ;
- valider au minimum sa syntaxe ;
- l'ajouter aux facilités d'exploitation si l'opérateur doit l'utiliser directement.
---
## Cycle de vie et conformité des VM
Set-OPS doit gérer le cycle de vie complet des VM Chezlepro :
```text
VM Debian minimale
→ goldenisation du template
→ clonage
→ identité initiale cloud-init
→ conformité par groupes Ansible
→ intégrations transversales
→ conformité continue
```
Le template est seulement la fondation. Les VM existantes et futures doivent converger vers l'état voulu par les playbooks de groupes.
Les rôles et playbooks doivent donc être conçus pour être relancés régulièrement, sans effet secondaire inutile.
Le groupe d'inventaire attendu pour les VM Debian gérées est :
```text
serveurs_debian
```
Les groupes spécialisés doivent s'ajouter selon les besoins réels, par exemple :
```text
clients_dns
clients_pki
clients_ldap
clients_supervision
clients_metriques
serveurs_web
serveurs_bases_donnees
```
Ne pas cibler `all` par défaut pour un playbook qui ne s'applique pas réellement à tous les hôtes.
---
## Services centraux et intégrations clientes
Pour chaque service d'infrastructure, distinguer deux responsabilités :
```text
service serveur : installe et configure le service central
intégration cliente : raccorde les VM au service central
```
Exemples :
```text
PowerDNS serveur → clients DNS / resolver / enregistrements
step-ca serveur → confiance CA / ACME client
LDAP serveur → SSSD / NSS / PAM client
Keycloak serveur → intégrations OIDC applicatives
Prometheus → node_exporter sur les VM
Icinga2 → agent ou checks distants
Grafana → datasources et dashboards côté plateforme
```
Quand un nouveau service central est ajouté, prévoir aussi le ou les rôles clients nécessaires pour intégrer les VM existantes et futures.
Les intégrations clientes doivent être :
- idempotentes ;
- activables par inventaire ou variables ;
- désactivées par défaut si leur dépendance centrale n'existe pas ;
- documentées avec leurs prérequis ;
- testables avec `--syntax-check` et, lorsque possible, `--check`.
Ne pas mettre les intégrations applicatives ou les agents spécialisés dans le golden template, sauf justification opérationnelle explicite.
---
## Variables et inventaires
Les variables de rôle doivent utiliser un préfixe correspondant au rôle.
Exemples :
```yaml
ssh_durcissement_port: 22
nftables_socle_enabled: false
fail2ban_ssh_enabled: true
```
Séparer clairement :
```text
variables de template : construction du golden template
variables de conformité : état voulu des VM déployées
variables applicatives : services et rôles spécialisés
```
Les variables de conformité ne doivent pas couper l'accès SSH, DNS ou réseau sans validation explicite.
Toute variable susceptible de provoquer une action destructive, bloquante ou irréversible doit exiger une confirmation explicite.
---
## Langue et nommage
La surface destinée à l'opérateur doit être en français.
À franciser :
- noms de groupes d'inventaire ;
- cibles `make` ;
- variables de commande maison ;
- libellés et messages des scripts maison ;
- noms des playbooks maison quand ils sont exposés à l'opérateur ;
- documentation d'exploitation.
À ne pas franciser automatiquement :
- mots-clés Ansible (`hosts`, `roles`, `tasks`, `handlers`, `vars`, `become`) ;
- noms de modules Ansible (`ansible.builtin.apt`, `ansible.builtin.template`, etc.) ;
- conventions techniques imposées par un outil ;
- noms de rôles existants, sauf demande explicite ou migration ciblée justifiée.
Les variables de rôle peuvent rester alignées sur le nom technique du rôle, mais elles doivent conserver un préfixe clair et stable.
Ne pas faire de renommage massif uniquement pour franciser si cela augmente le risque sans bénéfice opérationnel immédiat.
---
## Template Debian 13 Proxmox
@ -246,7 +446,7 @@ Il peut contenir :
- `cloud-guest-utils` ;
- chrony ;
- outils de diagnostic de base ;
- hardening raisonnable ;
- durcissement raisonnable ;
- AppArmor ;
- auditd ;
- fail2ban SSH ;
@ -277,23 +477,20 @@ Les services spécialisés doivent être installés ensuite par des playbooks d
## SSH
Pendant la construction du template, létat transitoire accepté est :
Le template Debian 13 Proxmox doit être construit avec un accès SSH par clé dès le départ.
```text
PasswordAuthentication yes
PermitRootLogin no
PubkeyAuthentication yes
```
Cloud-init doit injecter le `ciuser` et sa clé publique avant l'exécution d'Ansible.
État cible recommandé après validation des clés SSH :
État attendu :
```text
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AuthenticationMethods publickey
```
Ne jamais désactiver lauthentification par mot de passe avant davoir confirmé que laccès par clé fonctionne.
Ne pas lancer le playbook de préparation tant que l'accès SSH par clé au compte technique n'est pas confirmé.
Toute modification SSH doit valider la configuration avant rechargement :
@ -343,7 +540,7 @@ Le nettoyage final avant conversion en template doit être protégé par une con
Exemple :
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_cleanup.yml -e confirm_template_cleanup=true
ansible-playbook -i inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
```
Le nettoyage peut inclure :
@ -382,6 +579,22 @@ Les corrections de rôles, handlers, playbooks et templates doivent être consig
---
## Stabilisation des changements
Après une série cohérente de changements validés, recommander un commit propre avant de poursuivre vers un nouveau chantier.
Avant de recommander un commit :
- vérifier l'état Git ;
- résumer les fichiers créés, modifiés, déplacés ou supprimés ;
- indiquer les validations exécutées ;
- signaler les validations non exécutées ;
- mentionner les risques ou limites connus.
Ne pas créer de commit sans demande explicite.
---
## Comportement attendu de Codex
Avant de modifier :

View file

@ -1,15 +1,67 @@
# CHANGELOG — Set-OPS
## 2026-06-21
### Ajouté
- Ajout de facilités `make` pour exploiter Ansible sur les VM Debian déployées :
- diagnostic `ping`, `sudo` et `faits` ;
- variables `LIMITE`, `VERIFICATION`, `DIFF`, `ETIQUETTES`, `SAUTER_ETIQUETTES` et `VARIABLES`.
- Ajout de facilités `make` pour inspecter et valider les inventaires Ansible :
- `hote-ajouter` ;
- `hote-groupes` ;
- `hote-afficher` ;
- `inventaire-verifier` ;
- `inventaire-lister` ;
- `inventaire-graphe` ;
- `inventaire-hote` ;
- `inventaire-lab` ;
- `inventaire-production`.
- Ajout de `deployer` et `deployer-groupe` comme commandes opérateur simplifiées pour appliquer les playbooks de groupes.
- Ajout de `scripts/inventory_host.py` pour gérer les entrées d'inventaire et leur appartenance aux groupes sans édition YAML manuelle.
- Ajout de la convention `1 groupe opérationnel = 1 playbook homonyme` avec les premiers playbooks :
- `playbooks/groupes/serveurs_debian.yml` ;
- `playbooks/groupes/serveurs_durcis.yml`.
- Ajout du playbook `playbooks/maintenance/verifier_hote_debian.yml` pour valider une VM Debian 13 gérée par Set-OPS après socle et durcissement.
- Ajout de directives `AGENTS.md` pour formaliser le `Makefile` comme interface opérateur, la convention groupe/playbook, la francisation de la surface opérateur et les points de stabilisation avant commit.
- Ajout du playbook `playbooks/proxmox/cloner_vm_debian.yml` et des cibles `make cloner-vm` / `make creer-vm` pour créer des clones Debian via l'API Proxmox sans stocker de secret dans le dépôt.
- Ajout de `inventories/lab/group_vars/proxmox.yml` pour les paramètres Proxmox non sensibles et de `proxmox.vault.yml.example` pour préparer les secrets API avec Ansible Vault.
- Ajout de `make config` pour renseigner les paramètres Proxmox et créer ou éditer le Vault API depuis le terminal.
### Modifié
- Francisation de la surface opérateur :
- variables `make` ;
- cibles `make` ;
- groupes d'inventaire ;
- chemins des playbooks maison ;
- libellés du script de gestion d'inventaire.
- Documentation du flux de déploiement d'une VM clonée depuis le modèle doré avec les commandes `make`.
- Optimisation de la procédure de bootstrap du modèle Debian 13 pour utiliser Cloud-Init comme source unique de l'identité initiale au lieu de maintenir une création manuelle parallèle du compte et de la clé SSH.
- Simplification de `make cloner-vm` et `make creer-vm` pour que les paramètres Proxmox communs viennent des variables Ansible plutôt que de la ligne de commande ou de l'environnement.
- Ajustement de `make config` pour ne plus redemander les valeurs Cloud-Init déjà portées par le modèle Proxmox.
- Déplacement du VLAN hors de `make config` afin de l'exiger explicitement à chaque création de VM avec `VLAN=...`.
- Retrait des options `DEMANDER_MDP` et `DEMANDER_SUDO` du `Makefile`; le flux suppose SSH par clé et sudo NOPASSWD déjà fonctionnels.
- Retrait des playbooks de couches redondants `playbooks/socle/debian_commun.yml` et `playbooks/durcissement/debian_serveur_durcissement.yml`; la conformité des VM déployées passe maintenant par `playbooks/groupes/`.
- Bonification de `AGENTS.md` pour désigner `playbooks/groupes/` comme source officielle de conformité et éviter les cibles `make` de couches parallèles.
- Simplification de l'aide du `Makefile` pour exposer d'abord les gestes opérateur : inventaire, groupes, déploiement.
- Clarification de `make aide` pour expliquer les différences entre `creer-vm`, `cloner-vm`, `deployer`, `deployer-groupe` et `appliquer`.
- Réorganisation de `make aide` par objets d'exploitation : VM, hôtes, groupes, inventaires, modèle et validation.
- Retrait des commandes opérateur explicites de ping et sudo; ces vérifications sont maintenant implicites dans `deployer` et `preparer-modele`.
- Ajout d'une demande automatique de mot de passe Ansible Vault pour `make cloner-vm` et `make creer-vm` lorsqu'un `proxmox.vault.yml` chiffré est présent.
- Amélioration du message d'erreur lorsque `proxmox.vault.yml` ne peut pas être chargé.
- Ajustement du playbook Proxmox pour laisser apparaître les erreurs API utiles sans masquer entièrement les échecs de clonage.
- Modification de `make deployer HOTE=...` pour appliquer les playbooks correspondant aux groupes de l'hôte.
- Ajout d'une vérification explicite de la bibliothèque Python `proxmoxer` avant les appels API Proxmox.
- Retrait de la taille disque clone des paramètres Proxmox persistés afin de ne pas tenter de réduire un disque hérité du modèle.
## 2026-06-19
### Ajouté
- Structure initiale globale du dépôt Set-OPS.
- Playbooks de création du template Debian 13 Proxmox.
- Rôles de base Debian.
- Rôles de hardening template-safe.
- Rôles de durcissement template-safe.
- Playbook de vérification.
- Playbook de nettoyage final avant conversion en template.
# CHANGELOG — Set-OPS
## 2026-06-20
@ -26,10 +78,10 @@
- préférence pour les correctifs ciblés au lieu des régénérations massives ;
- rappel que `Set-OPS` ne doit pas être restructuré autour dun seul besoin ponctuel.
- Ajout dune logique de template Debian 13 Proxmox :
- `playbooks/vm_templates/debian13_proxmox_prepare.yml` ;
- `playbooks/vm_templates/debian13_proxmox_verify.yml` ;
- `playbooks/vm_templates/debian13_proxmox_cleanup.yml`.
- Ajout dune couche de hardening template-safe :
- `playbooks/modeles_vm/debian13_proxmox_preparer.yml` ;
- `playbooks/modeles_vm/debian13_proxmox_verifier.yml` ;
- `playbooks/modeles_vm/debian13_proxmox_nettoyer.yml`.
- Ajout dune couche de durcissement template-safe :
- AppArmor ;
- auditd ;
- fail2ban SSH ;
@ -37,6 +89,40 @@
- journald ;
- sysctl de sécurité ;
- nftables installé et préparé, mais désactivé par défaut dans le template.
- Ajout dun document darchitecture pour les intégrations futures des VM :
- supervision ;
- métriques ;
- identité ;
- PKI interne ;
- DNS interne.
- Documentation détaillée des ajouts appliqués au modèle doré Debian 13 Proxmox depuis une installation minimale.
- Ajout d'un runbook explicite pour le playbook de préparation du modèle doré Debian 13 Proxmox.
- Ajout d'un runbook manuel précis pour le bootstrap initial de la VM vanille Debian 13 :
- correction des sources APT après installation depuis DVD ;
- installation minimale de `sudo`, SSH, `cloud-init`, `cloud-guest-utils` et `qemu-guest-agent` ;
- utilisation de Cloud-Init pour injecter le compte `ansible`, sa clé SSH, le réseau et le point de bascule vers le `Makefile`.
- Ajout d'un document de cycle de vie des VM Chezlepro :
- VM vanille ;
- goldenisation ;
- clonage ;
- socle ;
- durcissement ;
- conformité continue.
- Ajout de directives `AGENTS.md` pour la convergence des VM existantes et futures :
- cycle de vie des VM ;
- distinction service central et intégration cliente ;
- groupes d'inventaire par intégration ;
- séparation des variables de template, conformité et services.
- Ajout d'un `Makefile` comme interface d'exploitation courante :
- validation ;
- syntax-checks ;
- préparation, vérification et nettoyage protégé du template ;
- socle et durcissement des VM Debian.
- Durcissement SSH du template dès la construction :
- accès par clé publique obligatoire ;
- authentification par mot de passe désactivée ;
- forwarding et tunnels SSH désactivés par défaut ;
- limites de tentatives, sessions et démarrages SSH resserrées.
### Modifié
- Renforcement attendu de `CLAUDE.md` pour rappeler que `AGENTS.md` est la source dautorité principale.
@ -47,15 +133,31 @@
- valider les playbooks touchés ;
- signaler les tests non exécutés ;
- mettre à jour `CHANGELOG.md` si pertinent.
- Migration des variables de rôles vers des préfixes propres à chaque rôle pour respecter `ansible-lint`.
- Mise à jour de la variable de confirmation du nettoyage final :
- `template_cleanup_confirm`.
- Correction de la structure du `CHANGELOG.md` pour retirer le titre dupliqué.
- Simplification ciblée de l'arborescence `roles/` :
- retrait des doublons non utilisés sous `roles/base/` ;
- retrait des doublons non utilisés sous `roles/security/` ;
- retrait des doublons non utilisés sous `roles/vm_template/`.
- Ajout d'un renvoi depuis la procédure manuelle vers la justification du golden template.
- Séparation documentaire et opérationnelle entre variables de template et variables de conformité.
- Extension du playbook de groupe `playbooks/groupes/serveurs_debian.yml` pour maintenir le socle commun des VM Debian déployées.
- Mise à jour de la documentation pour exiger un `ciuser` cloud-init avec clé publique avant l'exécution d'Ansible.
- Déplacement des listes de paquets communes dans les defaults des rôles `common_packages` et `hardening_packages`.
- Déplacement des variables de construction du template dans `inventories/lab/group_vars/modeles_vm.yml`.
- Déplacement des variables de conformité Debian dans `inventories/production/group_vars/serveurs_debian.yml`.
- Ciblage du playbook de durcissement sur `serveurs_debian` au lieu de `all`.
- Renforcement du playbook de vérification du modèle doré.
### Corrigé à appliquer
- Vérifier tous les rôles contenant `notify`.
- Ajouter les handlers manquants dans les rôles concernés, notamment :
### Corrigé
- Vérification des rôles contenant `notify`.
- Confirmation des handlers SSH requis dans les rôles concernés :
- `roles/ssh_baseline/handlers/main.yml` ;
- `roles/ssh_hardening/handlers/main.yml`.
### À valider
- Vérifier que chaque `notify` pointe vers un handler existant.
- Relancer le playbook de préparation du template.
- Lancer le playbook de vérification.
- Ne pas lancer le nettoyage final avant validation complète de la VM.
### Validé
- Vérification que chaque `notify` du template Debian 13 Proxmox pointe vers un handler existant.
- Relance du playbook de préparation du template Debian 13 Proxmox.
- Nettoyage final non lancé.

358
Makefile Normal file
View file

@ -0,0 +1,358 @@
SHELL := /usr/bin/env bash
export ANSIBLE_HOME ?= $(CURDIR)/.ansible
export ANSIBLE_LOCAL_TEMP ?= $(CURDIR)/.ansible/tmp
export ANSIBLE_SSH_CONTROL_PATH_DIR ?= $(CURDIR)/.ansible/cp
export ANSIBLE_SSH_ARGS ?= -F /dev/null -o ControlMaster=no
INVENTAIRE_LAB ?= inventories/lab/hosts.yml
INVENTAIRE_PRODUCTION ?= inventories/production/hosts.yml
FICHIER_INVENTAIRE ?= $(INVENTAIRE_PRODUCTION)
GROUPE_MODELE ?= modeles_vm
GROUPE_DEBIAN ?= serveurs_debian
LIMITE ?= $(GROUPE_DEBIAN)
HOTE ?=
ADRESSE_IP ?=
GROUPES ?= $(GROUPE_DEBIAN)
GROUPE ?= $(GROUPE_DEBIAN)
UTILISATEUR_ANSIBLE ?= ansible
VMID_MODELE ?=
VMID ?=
NOEUD_PROXMOX ?=
STOCKAGE_PROXMOX ?=
FORMAT_DISQUE ?=
TAILLE_DISQUE ?=
DISQUE_PROXMOX ?=
CIDR ?= 24
PASSERELLE ?=
DNS ?=
DHCP ?= false
CIUSER ?=
CLE_SSH_PUBLIQUE ?=
PONT_PROXMOX ?=
VLAN ?=
DEMARRER ?=
CLONE_COMPLET ?=
CONFIRMER ?= false
VERIFICATION ?= false
DIFF ?= false
ETIQUETTES ?=
SAUTER_ETIQUETTES ?=
VARIABLES ?=
OPTIONS_PLAYBOOK :=
ifneq ($(LIMITE),)
OPTIONS_PLAYBOOK += --limit $(LIMITE)
endif
ifeq ($(VERIFICATION),true)
OPTIONS_PLAYBOOK += --check
endif
ifeq ($(DIFF),true)
OPTIONS_PLAYBOOK += --diff
endif
ifneq ($(ETIQUETTES),)
OPTIONS_PLAYBOOK += --tags $(ETIQUETTES)
endif
ifneq ($(SAUTER_ETIQUETTES),)
OPTIONS_PLAYBOOK += --skip-tags $(SAUTER_ETIQUETTES)
endif
ifneq ($(VARIABLES),)
OPTIONS_PLAYBOOK += -e $(VARIABLES)
endif
PLAYBOOK_PREPARER_MODELE := playbooks/modeles_vm/debian13_proxmox_preparer.yml
PLAYBOOK_VERIFIER_MODELE := playbooks/modeles_vm/debian13_proxmox_verifier.yml
PLAYBOOK_NETTOYER_MODELE := playbooks/modeles_vm/debian13_proxmox_nettoyer.yml
PLAYBOOK_VERIFIER_HOTE := playbooks/maintenance/verifier_hote_debian.yml
PLAYBOOK_PROXMOX_CLONER_VM := playbooks/proxmox/cloner_vm_debian.yml
DOSSIER_PLAYBOOKS_GROUPES := playbooks/groupes
.DEFAULT_GOAL := aide
.PHONY: ansible-runtime
ansible-runtime:
@mkdir -p "$(ANSIBLE_LOCAL_TEMP)"
@mkdir -p "$(ANSIBLE_SSH_CONTROL_PATH_DIR)"
.PHONY: aide
aide:
@printf '%s\n' 'Set-OPS'
@printf '%s\n' ''
@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' ' 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' ' Configurer Proxmox et le Vault API:'
@printf '%s\n' ' make config'
@printf '%s\n' ''
@printf '%s\n' 'Hotes'
@printf '%s\n' ' Remettre un hote en conformite selon ses groupes:'
@printf '%s\n' ' make deployer HOTE=web-01'
@printf '%s\n' ' Afficher un hote:'
@printf '%s\n' ' make hote-afficher HOTE=web-01'
@printf '%s\n' ' Ajouter un hote existant:'
@printf '%s\n' ' make hote-ajouter HOTE=web-01 ADRESSE_IP=192.168.12.101 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' ' Diagnostiquer:'
@printf '%s\n' ' make verifier-hote LIMITE=web-01'
@printf '%s\n' ''
@printf '%s\n' 'Groupes'
@printf '%s\n' ' Appliquer un groupe complet:'
@printf '%s\n' ' make deployer-groupe GROUPE=serveurs_debian'
@printf '%s\n' ' Rejouer directement le playbook d un groupe:'
@printf '%s\n' ' make appliquer GROUPE=serveurs_debian'
@printf '%s\n' ' Convention:'
@printf '%s\n' ' groupe serveurs_debian -> playbooks/groupes/serveurs_debian.yml'
@printf '%s\n' ''
@printf '%s\n' 'Inventaires'
@printf '%s\n' ' Graphe de production:'
@printf '%s\n' ' make inventaire'
@printf '%s\n' ' Graphe explicite:'
@printf '%s\n' ' make inventaire-graphe FICHIER_INVENTAIRE=inventories/production/hosts.yml'
@printf '%s\n' ' Verifier les inventaires:'
@printf '%s\n' ' make inventaire-verifier'
@printf '%s\n' ' Lister les donnees brutes:'
@printf '%s\n' ' make inventaire-lister'
@printf '%s\n' ''
@printf '%s\n' 'Modele Debian 13 Proxmox'
@printf '%s\n' ' Construire et verifier:'
@printf '%s\n' ' make preparer-modele'
@printf '%s\n' ' make verifier-modele'
@printf '%s\n' ' Nettoyage final protege:'
@printf '%s\n' ' make nettoyer-modele CONFIRMER=true'
@printf '%s\n' ''
@printf '%s\n' 'Validation'
@printf '%s\n' ' make syntaxe'
@printf '%s\n' ' make lint'
@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' ' FICHIER_INVENTAIRE=inventories/production/hosts.yml CONFIRMER=true'
.PHONY: lint
lint: ansible-runtime
ansible-lint
.PHONY: syntaxe syntaxe-modele syntaxe-nettoyage syntaxe-verification-modele syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox
syntaxe: syntaxe-modele syntaxe-verification-modele syntaxe-nettoyage syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox
syntaxe-modele: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_PREPARER_MODELE) --syntax-check
syntaxe-verification-modele: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_VERIFIER_MODELE) --syntax-check
syntaxe-nettoyage: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_NETTOYER_MODELE) --syntax-check
syntaxe-verification-hote: ansible-runtime
ansible-playbook -i $(INVENTAIRE_PRODUCTION) $(PLAYBOOK_VERIFIER_HOTE) --syntax-check
syntaxe-groupes: ansible-runtime
@for playbook in $(DOSSIER_PLAYBOOKS_GROUPES)/*.yml; do \
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --syntax-check; \
done
syntaxe-proxmox: ansible-runtime
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) --syntax-check
.PHONY: verifier
verifier: lint syntaxe
.PHONY: inventaire hote-ajouter hote-groupes hote-afficher appliquer deployer deployer-groupe cloner-vm creer-vm config inventaire-verifier inventaire-lister inventaire-graphe inventaire-hote inventaire-lab inventaire-production
inventaire: inventaire-production
config:
python3 scripts/config_proxmox.py
hote-ajouter: ansible-runtime
@if [[ -z "$(HOTE)" || -z "$(ADRESSE_IP)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote ADRESSE_IP=adresse_ip.'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) ajouter --hote $(HOTE) --adresse-ip $(ADRESSE_IP) --utilisateur-ansible $(UTILISATEUR_ANSIBLE) --groupes "$(GROUPES)"
$(MAKE) inventaire-verifier
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
hote-groupes: ansible-runtime
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote GROUPES="groupe1 groupe2".'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) definir-groupes --hote $(HOTE) --groupes "$(GROUPES)"
$(MAKE) inventaire-verifier
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
hote-afficher: ansible-runtime
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) afficher --hote $(HOTE)
appliquer: ansible-runtime
@if [[ -z "$(GROUPE)" ]]; then \
printf '%s\n' 'Refus: relancer avec GROUPE=nom_groupe.'; \
exit 2; \
fi
@if [[ ! -f "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" ]]; then \
printf '%s\n' 'Refus: aucun playbook pour ce groupe: $(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml'; \
exit 2; \
fi
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit $(GROUPE)
deployer:
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi
$(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 $$?; \
if [[ -z "$$playbooks" ]]; then \
printf '%s\n' 'Refus: aucun playbook applicable pour HOTE=$(HOTE).'; \
exit 2; \
fi; \
for playbook in $$playbooks; do \
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)"; \
done
$(MAKE) verifier-hote LIMITE="$(HOTE)"
deployer-groupe:
@if [[ -z "$(GROUPE)" ]]; then \
printf '%s\n' 'Refus: relancer avec GROUPE=nom_groupe.'; \
exit 2; \
fi
$(MAKE) appliquer GROUPE="$(GROUPE)"
cloner-vm: ansible-runtime
@if [[ -z "$(HOTE)" || -z "$(VMID)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom VMID=id_clone.'; \
exit 2; \
fi
@if [[ -z "$(VLAN)" ]]; then \
printf '%s\n' 'Refus: relancer avec VLAN=id_vlan.'; \
exit 2; \
fi
@if ! [[ "$(VLAN)" =~ ^[0-9]+$$ ]] || (( 10#$(VLAN) < 1 || 10#$(VLAN) > 4094 )); then \
printf '%s\n' 'Refus: VLAN doit etre un nombre entre 1 et 4094.'; \
exit 2; \
fi
@if [[ -n "$(CLE_SSH_PUBLIQUE)" && ! -f "$(CLE_SSH_PUBLIQUE)" ]]; then \
printf '%s\n' 'Refus: cle publique SSH introuvable: $(CLE_SSH_PUBLIQUE)'; \
exit 2; \
fi
@if [[ "$(DHCP)" != "true" && ( -z "$(ADRESSE_IP)" || -z "$(CIDR)" || -z "$(PASSERELLE)" ) ]]; then \
printf '%s\n' 'Refus: fournir ADRESSE_IP, CIDR et PASSERELLE, ou utiliser DHCP=true.'; \
exit 2; \
fi
@ipconfig='ip=dhcp'; \
if [[ "$(DHCP)" != "true" ]]; then \
ipconfig='ip=$(ADRESSE_IP)/$(CIDR),gw=$(PASSERELLE)'; \
fi; \
extra_vars=( \
-e proxmox_clone_nom="$(HOTE)" \
-e proxmox_clone_vmid="$(VMID)" \
-e proxmox_clone_ipconfig0="$$ipconfig" \
); \
[[ -n "$(VMID_MODELE)" ]] && extra_vars+=( -e proxmox_clone_vmid_modele="$(VMID_MODELE)" ); \
[[ -n "$(NOEUD_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_noeud="$(NOEUD_PROXMOX)" ); \
[[ -n "$(STOCKAGE_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_stockage="$(STOCKAGE_PROXMOX)" ); \
[[ -n "$(FORMAT_DISQUE)" ]] && extra_vars+=( -e proxmox_clone_format="$(FORMAT_DISQUE)" ); \
[[ -n "$(CLONE_COMPLET)" ]] && extra_vars+=( -e proxmox_clone_complet="$(CLONE_COMPLET)" ); \
[[ -n "$(TAILLE_DISQUE)" ]] && extra_vars+=( -e proxmox_clone_taille_disque="$(TAILLE_DISQUE)" ); \
[[ -n "$(DISQUE_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_disque="$(DISQUE_PROXMOX)" ); \
[[ -n "$(DNS)" ]] && extra_vars+=( -e proxmox_clone_dns="$(DNS)" ); \
[[ -n "$(CIUSER)" ]] && extra_vars+=( -e proxmox_clone_ciuser="$(CIUSER)" ); \
[[ -n "$(CLE_SSH_PUBLIQUE)" ]] && extra_vars+=( -e proxmox_clone_cle_publique_fichier="$(CLE_SSH_PUBLIQUE)" ); \
[[ -n "$(PONT_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_pont="$(PONT_PROXMOX)" ); \
extra_vars+=( -e proxmox_clone_vlan="$(VLAN)" ); \
[[ -n "$(DEMARRER)" ]] && extra_vars+=( -e proxmox_clone_demarrer="$(DEMARRER)" ); \
vault_args=(); \
vault_file="inventories/lab/group_vars/proxmox.vault.yml"; \
if [[ -f "$$vault_file" ]]; then \
read -r premiere_ligne < "$$vault_file" || true; \
case "$$premiere_ligne" in \
'$$ANSIBLE_VAULT'*) \
if [[ -z "$${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then \
vault_args+=( --ask-vault-pass ); \
fi; \
;; \
esac; \
fi; \
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) "$${vault_args[@]}" "$${extra_vars[@]}"
creer-vm:
@if [[ -z "$(ADRESSE_IP)" ]]; then \
printf '%s\n' 'Refus: creer-vm doit recevoir ADRESSE_IP pour inscrire l hote dans l inventaire.'; \
exit 2; \
fi
$(MAKE) cloner-vm HOTE="$(HOTE)" VMID_MODELE="$(VMID_MODELE)" VMID="$(VMID)" NOEUD_PROXMOX="$(NOEUD_PROXMOX)" STOCKAGE_PROXMOX="$(STOCKAGE_PROXMOX)" FORMAT_DISQUE="$(FORMAT_DISQUE)" TAILLE_DISQUE="$(TAILLE_DISQUE)" DISQUE_PROXMOX="$(DISQUE_PROXMOX)" ADRESSE_IP="$(ADRESSE_IP)" CIDR="$(CIDR)" PASSERELLE="$(PASSERELLE)" DNS="$(DNS)" DHCP="$(DHCP)" CIUSER="$(CIUSER)" CLE_SSH_PUBLIQUE="$(CLE_SSH_PUBLIQUE)" PONT_PROXMOX="$(PONT_PROXMOX)" VLAN="$(VLAN)" DEMARRER="$(DEMARRER)" CLONE_COMPLET="$(CLONE_COMPLET)"
$(MAKE) hote-ajouter HOTE="$(HOTE)" ADRESSE_IP="$(ADRESSE_IP)" GROUPES="$(GROUPES)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
inventaire-verifier: ansible-runtime
ansible-inventory -i $(INVENTAIRE_LAB) --list > /dev/null
ansible-inventory -i $(INVENTAIRE_PRODUCTION) --list > /dev/null
inventaire-lister: ansible-runtime
ansible-inventory -i $(FICHIER_INVENTAIRE) --list
inventaire-graphe: ansible-runtime
ansible-inventory -i $(FICHIER_INVENTAIRE) --graph
inventaire-hote: ansible-runtime
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi
ansible-inventory -i $(FICHIER_INVENTAIRE) --host $(HOTE)
inventaire-lab:
$(MAKE) inventaire-graphe FICHIER_INVENTAIRE="$(INVENTAIRE_LAB)"
inventaire-production:
$(MAKE) inventaire-graphe FICHIER_INVENTAIRE="$(INVENTAIRE_PRODUCTION)"
.PHONY: _verifier-acces-modele _verifier-privileges-modele preparer-modele verifier-modele nettoyer-modele
_verifier-acces-modele: ansible-runtime
ansible -i $(INVENTAIRE_LAB) $(GROUPE_MODELE) -m ping -e ansible_become=false
_verifier-privileges-modele: ansible-runtime
ansible -i $(INVENTAIRE_LAB) $(GROUPE_MODELE) -b -m command -a "whoami"
preparer-modele: ansible-runtime _verifier-acces-modele _verifier-privileges-modele
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_PREPARER_MODELE)
verifier-modele: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_VERIFIER_MODELE)
nettoyer-modele: ansible-runtime
@if [[ "$(CONFIRMER)" != "true" ]]; then \
printf '%s\n' 'Refus: relancer avec CONFIRMER=true pour le nettoyage final du modele.'; \
exit 2; \
fi
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_NETTOYER_MODELE) -e template_cleanup_confirm=true
.PHONY: _verifier-acces-hote _verifier-privileges-hote faits verifier-hote
_verifier-acces-hote: ansible-runtime
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -m ping -e ansible_become=false
_verifier-privileges-hote: ansible-runtime
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -b -m command -a "whoami"
faits: ansible-runtime
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -m setup -a "filter=ansible_distribution*"
verifier-hote: ansible-runtime
ansible-playbook -i $(INVENTAIRE_PRODUCTION) $(PLAYBOOK_VERIFIER_HOTE) $(OPTIONS_PLAYBOOK)

View file

@ -5,8 +5,7 @@ Dépôt Ansible central pour construire, configurer, maintenir et documenter les
## Domaines prévus
- templates de VM Proxmox ;
- socle Debian ;
- hardening ;
- groupes de conformité ;
- maintenance ;
- web ;
- bases de données ;
@ -20,9 +19,9 @@ Dépôt Ansible central pour construire, configurer, maintenir et documenter les
Template Debian 13 Proxmox :
```text
playbooks/vm_templates/debian13_proxmox_prepare.yml
playbooks/vm_templates/debian13_proxmox_verify.yml
playbooks/vm_templates/debian13_proxmox_cleanup.yml
playbooks/modeles_vm/debian13_proxmox_preparer.yml
playbooks/modeles_vm/debian13_proxmox_verifier.yml
playbooks/modeles_vm/debian13_proxmox_nettoyer.yml
```
## Principe
@ -37,3 +36,72 @@ Les services spécialisés seront installés ensuite sur les clones :
- Docker/Podman ;
- monitoring complet ;
- applications métier.
## Exploitation courante
Un `Makefile` fournit les commandes d'exploitation principales.
Afficher l'aide :
```bash
make
```
Valider le dépôt :
```bash
make verifier
```
Inspecter les inventaires :
```bash
make inventaire
make hote-afficher HOTE=web-01
```
Ajouter une VM à l'inventaire production et l'associer aux groupes qui détermineront sa configuration :
```bash
make hote-ajouter HOTE=web-01 ADRESSE_IP=192.168.12.101 GROUPES="serveurs_debian serveurs_durcis"
make hote-groupes HOTE=web-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.
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
```
Les valeurs Cloud-Init communes déjà présentes dans le modèle Proxmox sont héritées par les clones.
Préparer le golden template Debian 13 Proxmox :
```bash
make preparer-modele
make verifier-modele
```
Le nettoyage final du template est protégé :
```bash
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
```
Déployer ou remettre en conformité un groupe :
```bash
make deployer-groupe GROUPE=serveurs_debian
```

View file

@ -70,7 +70,7 @@ Il ne doit pas contenir :
Le playbook :
```text
playbooks/vm_templates/debian13_proxmox_prepare.yml
playbooks/modeles_vm/debian13_proxmox_preparer.yml
```
a échoué sur un handler manquant :
@ -91,7 +91,7 @@ Rôles à vérifier en priorité :
```text
roles/ssh_baseline/
roles/ssh_hardening/
roles/ssh_durcissement/
```
Chaque rôle utilisant `notify` doit avoir son propre fichier :
@ -110,23 +110,23 @@ Créer ou corriger :
```text
roles/ssh_baseline/handlers/main.yml
roles/ssh_hardening/handlers/main.yml
roles/ssh_durcissement/handlers/main.yml
```
Contenu attendu :
```yaml
---
- name: Validate and reload ssh
block:
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
listen: Validate and reload ssh
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
listen: Validate and reload ssh
```
Puis vérifier tous les `notify` :
@ -143,19 +143,19 @@ find roles -path '*/handlers/main.yml' -print
Après correction :
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_prepare.yml --syntax-check
make syntax-template
```
Puis relancer :
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_prepare.yml
make preparer-modele
```
Puis vérifier :
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_verify.yml
make verifier-modele
```
---
@ -165,7 +165,7 @@ ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_pr
Ne lancer le nettoyage final que lorsque la VM est validée :
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_cleanup.yml -e confirm_template_cleanup=true
make nettoyer-modele CONFIRMER=true
```
Ensuite seulement :

View file

@ -5,14 +5,59 @@ Set-OPS est un dépôt global d'exploitation.
## Domaines
```text
baseline : socle commun Debian
hardening : sécurité
socle : socle commun Debian
durcissement : sécurité
maintenance : mises à jour
monitoring : supervision
proxmox : hyperviseurs
vm_templates : modèles de VM
modeles_vm : modèles de VM
web : services web
database : bases de données
backup : sauvegardes
applications : services applicatifs
```
## Rôles Ansible
Les rôles actifs sont conservés directement sous `roles/`.
Les sous-arborescences catégorielles de rôles ne doivent pas dupliquer un rôle actif. Un nouveau rôle doit être ajouté seulement lorsqu'un besoin opérationnel réel existe.
Les valeurs communes aux rôles doivent rester dans les defaults des rôles quand elles sont valables pour tous les environnements. Les inventaires ne doivent contenir que les politiques propres à leur contexte.
## Groupes et playbooks
Les groupes opérationnels de l'inventaire doivent correspondre à un playbook homonyme :
```text
inventories/production/hosts.yml
serveurs_debian
playbooks/groupes/serveurs_debian.yml
```
Cette relation rend l'exploitation lisible :
```text
appartenance à un groupe
→ playbook correspondant
→ état voulu appliqué par Ansible
```
Les groupes structurels ou vides peuvent exister, mais un groupe assigné à une VM de production doit avoir un playbook s'il exprime une intention de configuration.
## Cycle de vie VM
Le cycle de vie des VM Chezlepro est documenté dans :
```text
docs/vm-lifecycle.md
```
## Intégrations futures
Les intégrations transversales des VM sont documentées dans :
```text
docs/integrations-vm.md
```

43
docs/integrations-vm.md Normal file
View file

@ -0,0 +1,43 @@
# Intégrations futures des VM
Les serveurs centraux ne sont pas encore en place. Les rôles d'intégration doivent donc rester optionnels et désactivés par défaut tant que leurs dépendances n'existent pas.
## Séparation
```text
Template Debian 13 Proxmox : socle commun minimal
Cloud-init : identité initiale du clone
Set-OPS + Ansible : configuration réelle du serveur
```
Le template ne doit pas être modifié pour chaque nouveau serveur. Les intégrations sont appliquées après clonage selon les groupes d'inventaire et les playbooks dédiés.
## Domaines prévus
```text
DNS interne : PowerDNS, résolveurs, enregistrements applicatifs
PKI interne : step-ca, ACME, confiance CA locale
Identité : LDAP, SSSD, Keycloak/OIDC selon le besoin
Supervision : Icinga2 agent ou checks distants
Métriques : Prometheus node_exporter
Visualisation : Grafana côté plateforme centrale
```
## Principes Ansible
- Chaque rôle d'intégration doit être désactivé par défaut.
- Chaque variable de rôle doit utiliser le préfixe du rôle.
- Les secrets ne doivent jamais être stockés en clair dans le dépôt.
- Un rôle ne doit pas échouer si son intégration est désactivée.
- Les playbooks applicatifs doivent être appliqués sur les clones, pas sur le template.
- Les opérations pouvant couper l'accès réseau ou SSH doivent exiger une confirmation explicite.
## Ordre logique de construction
1. DNS interne.
2. PKI interne et confiance CA.
3. Identité système et applicative.
4. Supervision et métriques.
5. Visualisation et alerting.
Cet ordre reste indicatif. Les rôles doivent rester découplés pour permettre une adoption progressive.

View file

@ -0,0 +1,312 @@
# Golden template Debian 13 Proxmox
## Objet
Ce document justifie les ajouts appliqués par Set-OPS à une installation Debian 13 minimale pour obtenir le golden template Proxmox de Chezlepro.
Le template doit rester un socle commun, clonable et sécuritaire. Il ne doit pas devenir un serveur applicatif ni intégrer des dépendances propres à un rôle final.
## État de départ
L'état de départ attendu est décrit dans :
```text
docs/procedure-template-debian13-proxmox.md
```
Résumé :
```text
Debian 13 minimal
aucun environnement graphique
SSH installé
partition EFI
partition racine ext4
pas de LVM
pas de swap
disque agrandissable
CloudInit Drive Proxmox présent
ciuser et clé SSH injectés par Cloud-Init
aucun secret
aucune donnée propre à un clone final
```
## Runbook du playbook prepare
### Objectif
Le playbook `debian13_proxmox_preparer.yml` transforme une VM Debian 13 minimale en socle golden template Chezlepro.
Il installe les composants communs, configure l'accès Ansible, applique le durcissement template-safe et prépare la VM pour cloud-init.
Il ne fait pas le nettoyage final avant conversion en template.
### Prérequis côté VM
Avant de lancer le playbook, la VM doit respecter ces conditions :
```text
Debian installé et démarré
réseau fonctionnel
SSH joignable depuis le poste Ansible
CloudInit Drive présent côté Proxmox
utilisateur Ansible initial injecté par Cloud-Init
clé publique SSH injectée par Cloud-Init
sudo ou accès root possible
APT fonctionnel
aucun rôle applicatif déjà installé
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
ansible -i inventories/lab/hosts.yml modeles_vm -m ping
ansible -i inventories/lab/hosts.yml modeles_vm -m setup
```
Adapter l'utilisateur et l'adresse IP selon `inventories/lab/hosts.yml`.
### Prérequis côté dépôt
Avant l'exécution :
```bash
git status --short
make syntax-template
```
L'inventaire doit contenir la VM dans le groupe :
```text
modeles_vm
```
Les variables du template sont dans :
```text
inventories/lab/group_vars/modeles_vm.yml
```
### Commande de préparation
Lancement lorsque l'accès par clé et sudo sont fonctionnels :
```bash
make preparer-modele
```
Le flux `make` suppose que SSH par clé et sudo NOPASSWD sont déjà fonctionnels pour `ansible`.
### Déroulement attendu
Le playbook :
1. vérifie que la cible est Debian ;
2. avertit si la version majeure n'est pas Debian 13 ;
3. installe les paquets communs ;
4. active `qemu-guest-agent` ;
5. installe et configure `cloud-init` ;
6. configure le compte technique Ansible ;
7. active la synchronisation du temps ;
8. configure SSH ;
9. applique le durcissement template-safe ;
10. prépare nftables sans l'activer ;
11. affiche le statut cloud-init.
### Résultat attendu
Une première exécution peut changer la VM.
Une relance sur une VM déjà préparée doit idéalement finir avec :
```text
failed=0
changed=0
```
Un résultat `changed=0` à la relance est le signal que le playbook est idempotent dans l'état actuel.
### Erreurs fréquentes
| Symptôme | Cause probable | Action |
| --- | --- | --- |
| `UNREACHABLE` | IP, SSH, utilisateur ou clé SSH incorrecte. | Vérifier `inventories/lab/hosts.yml`, cloud-init et tester `ssh`. |
| échec `become` | Sudo NOPASSWD absent ou utilisateur non autorisé. | Corriger l'accès sudo initial, puis relancer `make preparer-modele`. |
| échec APT | DNS, passerelle, miroir Debian ou verrou APT. | Vérifier réseau, DNS et processus APT en cours. |
| erreur handler SSH | Handler manquant ou nom `notify` incohérent. | Vérifier les handlers du rôle SSH avant de relancer. |
| avertissement Debian non 13 | La cible n'est pas Debian 13. | Ne pas convertir en template Debian 13 sans justification. |
### Point d'arrêt
Après `prepare`, ne pas convertir immédiatement la VM en template.
Exécuter d'abord :
```bash
make verifier-modele
```
Le playbook de vérification contrôle notamment Debian 13, les paquets requis, l'absence de swap actif, l'absence d'environnement graphique, les services de base, `nftables` désactivé, sudo NOPASSWD et le durcissement SSH effectif.
Le nettoyage final se lance seulement après validation complète :
```bash
make nettoyer-modele CONFIRMER=true
```
Ne pas lancer le cleanup final sur une VM encore en diagnostic ou sur un serveur final.
## Préparation Ansible
Le playbook applique les rôles suivants :
```text
common_packages
qemu_guest_agent
cloud_init
sudo_ansible
chrony
ssh_baseline
systemd_ssh_auto
motd
hardening_packages
sysctl_durcissement
core_dumps
unattended_upgrades
apparmor
auditd
fail2ban_ssh
journald
ssh_durcissement
nftables_socle
```
## Justification des ajouts
| Rôle | Ajout ou changement | Justification | Limite volontaire |
| --- | --- | --- | --- |
| `common_packages` | Installe les outils système et diagnostic de base. | Permet l'administration locale, le dépannage réseau, l'exécution fiable d'Ansible et les opérations de maintenance courantes. | Aucun service applicatif spécialisé n'est installé. |
| `qemu_guest_agent` | Installe et active `qemu-guest-agent`. | Donne à Proxmox une visibilité propre sur l'état de la VM et facilite les opérations d'arrêt, IP reporting et gestion depuis l'hyperviseur. | Ne remplace pas la supervision applicative. |
| `cloud_init` | Installe `cloud-init` et `cloud-guest-utils`, puis active les services cloud-init. | Donne au clone son identité initiale : hostname, utilisateur, clé SSH, IP, DNS et agrandissement de disque. | Cloud-init ne sert pas à configurer les applications. |
| `sudo_ansible` | Crée ou maintient le compte technique et son sudo NOPASSWD. | Permet l'administration automatisée par Ansible sans stocker de mot de passe dans le dépôt. | Ce compte doit rester technique et contrôlé. |
| `chrony` | Installe et active la synchronisation du temps. | Un temps correct est nécessaire pour TLS, journaux, audit, Kerberos/LDAP futur, monitoring et corrélation d'incidents. | La source NTP définitive peut être ajustée plus tard par inventaire. |
| `ssh_baseline` | Pose la base SSH : root interdit, clé publique obligatoire, mot de passe désactivé. | Force un accès administrateur par clé dès la construction du template. | Cloud-init doit avoir injecté le `ciuser` et sa clé avant Ansible. |
| `systemd_ssh_auto` | Désactive le comportement `systemd.ssh_auto` si demandé. | Évite une exposition SSH automatique via mécanismes transitoires non désirés dans un template serveur. | Ne remplace pas la politique SSH principale. |
| `motd` | Déploie un message d'accueil sobre. | Identifie le contexte Chezlepro et rappelle que la machine est gérée par Set-OPS. | Ne doit pas contenir d'information sensible. |
| `hardening_packages` | Installe les paquets de sécurité communs. | Fournit les briques nécessaires au durcissement sans configurer de service applicatif. | Les politiques strictes par rôle restent appliquées sur les clones. |
| `sysctl_durcissement` | Applique des paramètres noyau de sécurité. | Réduit des comportements réseau et noyau risqués avec des réglages standards pour serveur Linux. | Les réglages spécifiques à une charge de travail peuvent être ajustés par rôle. |
| `core_dumps` | Désactive les core dumps. | Limite le risque de fuite de secrets ou données sensibles dans des dumps mémoire. | Un serveur de debug peut réactiver un comportement adapté hors template. |
| `unattended_upgrades` | Configure les mises à jour automatiques de sécurité. | Réduit l'exposition aux vulnérabilités connues sur les clones qui restent proches du socle. | Les redémarrages automatiques restent désactivés par défaut. |
| `apparmor` | Installe et active AppArmor. | Ajoute une couche de confinement standard Debian avec faible coût opérationnel. | Les profils applicatifs spécifiques seront gérés par les rôles applicatifs. |
| `auditd` | Installe auditd et des règles de base. | Fournit une trace système utile pour exploitation, investigation et conformité minimale. | Les règles lourdes ou propres à une application ne vont pas dans le template. |
| `fail2ban_ssh` | Active une protection SSH simple contre les essais répétés. | Réduit le bruit et les attaques opportunistes sur SSH sans dépendre d'une plateforme centrale. | Ne remplace pas un pare-feu ni une politique d'accès réseau. |
| `journald` | Limite l'usage disque et la rétention des journaux. | Évite qu'une VM issue du template remplisse son disque à cause des logs système. | La centralisation des logs viendra plus tard si nécessaire. |
| `ssh_durcissement` | Ajoute les paramètres SSH plus stricts : port, délais, tentatives, keepalive, forwarding et tunnels désactivés. | Réduit la surface d'exposition SSH sans dépendre d'un rôle applicatif. | Les exceptions, comme un bastion ou du forwarding contrôlé, doivent être portées par un rôle dédié. |
| `nftables_socle` | Installe nftables et prépare une configuration, mais garde le service désactivé par défaut. | Prépare le standard pare-feu sans risquer de couper l'accès au template ou aux clones. | L'activation se fait par rôle serveur avec règles adaptées. |
## Paquets communs
Les paquets communs sont choisis pour l'exploitation réelle :
```text
administration : sudo, acl, bash-completion, tmux, vim, nano
transport : curl, wget, ca-certificates, gnupg
ansible : python3, python3-apt, python3-pip
diagnostic : htop, iotop, iftop, sysstat, lsof, ncdu, tree, file, less
réseau : dnsutils, iproute2, iputils-ping, net-tools, traceroute, mtr-tiny, tcpdump, netcat-openbsd, socat
maintenance : rsync, unzip, zip, tar, jq, logrotate, unattended-upgrades, apt-listchanges, needrestart
virtualisation : qemu-guest-agent, cloud-init, cloud-guest-utils
temps : chrony
```
Ces paquets sont acceptés dans le template parce qu'ils sont utiles sur presque tous les serveurs et ne transforment pas la VM en serveur applicatif.
## Choix de sécurité importants
### SSH
État attendu dès la construction :
```text
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AuthenticationMethods publickey
```
Le playbook `prepare` ne doit être lancé qu'après validation de l'accès SSH par clé au compte technique.
### Pare-feu
`nftables` est installé et préparé, mais désactivé par défaut.
Ce choix est volontaire : un pare-feu générique dans un template peut couper l'accès SSH ou bloquer un futur rôle serveur. L'activation doit être faite sur un clone avec des règles correspondant à son usage.
### Nettoyage final
Le nettoyage final est séparé et protégé par confirmation explicite. Il ne doit être lancé qu'au moment où la VM est validée et prête à être convertie en template.
## Vérification
```bash
make verifier-modele
```
## Validation minimale
Avant de déclarer le template prêt :
```bash
make verifier
make preparer-modele
make verifier-modele
```
La relance du playbook de préparation doit idéalement finir avec :
```text
failed=0
changed=0
```
## Nettoyage
```bash
make nettoyer-modele CONFIRMER=true
```
Ne pas lancer le nettoyage final sur un serveur de production ou sur une VM qui n'est pas destinée à devenir un template.
## Hors périmètre du template
Les éléments suivants ne doivent pas être installés dans le golden template :
```text
NGINX
PostgreSQL
MariaDB
Docker
Podman
Redis
GitLab
Nextcloud
monitoring complet
agents applicatifs spécialisés
données propres à un clone
secrets
clés privées
```
Les intégrations futures sont documentées dans :
```text
docs/integrations-vm.md
```
Le cycle complet VM vanille, golden template, clone et conformité continue est documenté dans :
```text
docs/vm-lifecycle.md
```

View file

@ -1,12 +1,18 @@
# Procédure manuelle — VM Debian 13 prête à convertir en template Proxmox
# Procédure manuelle — VM Debian 13 vanille pour Proxmox
## Objet du document
Ce document décrit le travail manuel à effectuer pour créer une VM Debian 13 propre dans Proxmox, jusquau moment où elle est prête à être convertie en modèle/template.
Ce document décrit le travail manuel à effectuer pour créer une VM Debian 13 vanille dans Proxmox, jusquau moment où elle peut être prise en charge par Set-OPS.
Le résultat attendu est une VM minimale, sobre, clonable, compatible avec cloud-init, prête à être prise en charge par Ansible et par le dépôt `Set-OPS`.
Le résultat attendu est une VM minimale, sobre, joignable en SSH, capable d'installer des paquets depuis les dépôts Debian, compatible avec cloud-init et prête à recevoir le playbook de préparation du golden template.
Ce document sarrête juste avant la conversion finale en template Proxmox.
Ce document sarrête au point de bascule vers le `Makefile`.
Les ajouts appliqués ensuite par Ansible, leur validation et leur justification sont documentés dans :
```text
docs/modeles_vm/debian13-proxmox.md
```
---
@ -19,10 +25,12 @@ Objectifs :
- système Debian 13 minimal ;
- aucun environnement graphique ;
- SSH actif ;
- compte technique `ansible` disponible ;
- `ansible` autorisé à utiliser sudo sans mot de passe ;
- `qemu-guest-agent` installé ;
- `cloud-init` installé ;
- disque CloudInit Proxmox attaché ;
- compte technique `ansible` injecté par cloud-init ;
- clé publique SSH injectée par cloud-init ;
- `ansible` autorisé à utiliser sudo ;
- partitionnement simple et agrandissable ;
- aucune partition swap bloquant la croissance du disque ;
- aucune donnée propre à une VM finale ;
@ -304,7 +312,7 @@ Le serveur web, les rôles applicatifs et les services métier seront installés
Après linstallation, démarrer la VM et se connecter en console ou par SSH.
Passer root :
Passer root si le compte root est disponible :
```bash
su -
@ -316,9 +324,55 @@ ou, si sudo est déjà disponible :
sudo -i
```
Toutes les commandes de bootstrap suivantes sont exécutées dans la VM.
---
## 13. Mise à jour du système
## 13. Réparer APT après installation DVD
Si Debian a été installé depuis l'ISO/DVD complet, APT peut garder une source `cdrom:`. Dans ce cas, `apt update` ou `apt install` échoue parce que le système cherche les paquets sur le média d'installation plutôt que sur les miroirs Debian.
Vérifier les sources :
```bash
grep -R "^[[:space:]]*deb cdrom:" /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true
```
Si une ligne `deb cdrom:` est présente, la commenter ou la retirer.
Configuration simple attendue pour Debian 13 :
```bash
cat > /etc/apt/sources.list <<'EOF'
deb http://deb.debian.org/debian trixie main contrib non-free-firmware
deb http://security.debian.org/debian-security trixie-security main contrib non-free-firmware
deb http://deb.debian.org/debian trixie-updates main contrib non-free-firmware
EOF
```
Si l'installation a créé des fichiers `.sources` sous `/etc/apt/sources.list.d/`, vérifier qu'ils ne contredisent pas cette configuration et qu'ils ne pointent pas vers `cdrom:`.
Vérifier le réseau avant de continuer :
```bash
ip route
cat /etc/resolv.conf
ping -c 3 1.1.1.1
ping -c 3 deb.debian.org
```
Résultat attendu :
```text
une route par défaut existe
un serveur DNS est configuré
le ping IP fonctionne
la résolution DNS fonctionne
```
---
## 14. Mise à jour du système
Exécuter :
@ -329,46 +383,38 @@ apt full-upgrade -y
---
## 14. Paquets de base
## 15. Paquets de bootstrap
Installer les paquets nécessaires au modèle :
Installer seulement les paquets nécessaires pour que Set-OPS puisse prendre le relais :
```bash
apt install -y \
sudo \
openssh-server \
qemu-guest-agent \
cloud-init \
cloud-guest-utils \
curl \
wget \
ca-certificates \
gnupg \
curl \
vim \
nano \
bash-completion \
htop \
lsof \
ncdu \
tree \
net-tools \
iproute2 \
dnsutils \
chrony
nano
```
Rôle des paquets principaux :
```text
sudo : délégation administrative
openssh-server : accès distant initial
qemu-guest-agent : communication Proxmox ↔ VM
cloud-init : personnalisation au premier boot du clone
cloud-guest-utils : fournit notamment growpart
chrony : synchronisation du temps
```
Les autres paquets communs, outils de diagnostic, chrony et composants de durcissement sont installés ensuite par `make preparer-modele`.
---
## 15. Activation du QEMU Guest Agent
## 16. Activation du QEMU Guest Agent
Activer le service :
@ -390,126 +436,21 @@ VM → Options → QEMU Guest Agent → Enabled
---
## 16. Configuration du compte ansible
## 17. Identité initiale par Cloud-Init
### Ajouter le compte au groupe sudo
À partir d'ici, ne pas créer manuellement le compte `ansible` ni son fichier `authorized_keys`, sauf dépannage.
```bash
usermod -aG sudo ansible
```
### Autoriser sudo sans mot de passe
Créer le fichier dédié :
```bash
cat > /etc/sudoers.d/90-ansible <<'EOF'
ansible ALL=(ALL) NOPASSWD:ALL
EOF
```
Appliquer les permissions :
```bash
chmod 440 /etc/sudoers.d/90-ansible
```
Valider la syntaxe :
```bash
visudo -cf /etc/sudoers.d/90-ansible
```
Le résultat attendu est :
L'identité initiale doit venir de Proxmox + Cloud-Init :
```text
/etc/sudoers.d/90-ansible: parsed OK
Proxmox : ciuser, clé SSH, réseau, DNS
Debian : cloud-init applique ces paramètres au démarrage
Ansible : configure ensuite le vrai socle du serveur
```
Tester depuis le compte `ansible` :
Cette séparation évite de maintenir deux méthodes concurrentes pour créer le même accès.
```bash
sudo -n true && echo OK
```
Résultat attendu :
```text
OK
```
---
## 17. SSH
### État transitoire
Pendant la construction du modèle, lauthentification par mot de passe peut rester active :
```text
PasswordAuthentication yes
```
Cest pratique tant que la clé SSH nest pas encore injectée.
### État cible
Quand laccès par clé SSH est confirmé, létat cible sera :
```text
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
```
Ne pas désactiver le login par mot de passe avant davoir confirmé que laccès par clé fonctionne.
### Tester une connexion SSH par mot de passe
Depuis un poste dadministration :
```bash
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no ansible@IP_DE_LA_VM
```
---
## 18. Cloud-init dans Debian
Vérifier que cloud-init est installé :
```bash
cloud-init --version
```
Vérifier le service :
```bash
systemctl status cloud-init --no-pager
```
Cloud-init servira à appliquer, au premier démarrage dun clone :
- hostname ;
- utilisateur initial ;
- clé SSH publique ;
- IP ;
- passerelle ;
- DNS ;
- agrandissement de la partition racine si le disque a été étendu.
Cloud-init ne doit pas remplacer Ansible pour les rôles serveur complexes.
Séparation recommandée :
```text
cloud-init : identité initiale de la VM
Ansible : configuration réelle du serveur
```
---
## 19. Ajout du CloudInit Drive dans Proxmox
### Ajouter le CloudInit Drive
Éteindre la VM :
@ -535,19 +476,11 @@ Exemple :
qm set 9000 --ide2 local-lvm:cloudinit
```
ou :
```bash
qm set 9000 --ide2 truenas-lvm:cloudinit
```
Le nom exact du stockage dépend de la configuration Proxmox.
---
### Définir les paramètres Cloud-Init du modèle
## 20. Paramètres cloud-init dans Proxmox
Après ajout du CloudInit Drive, Proxmox permet de définir les paramètres cloud-init.
Pour la VM qui deviendra le modèle, utiliser une identité technique minimale, pas une identité de serveur final.
Exemple DHCP :
@ -557,7 +490,7 @@ qm set 9000 --sshkeys ~/.ssh/id_ed25519.pub
qm set 9000 --ipconfig0 ip=dhcp
```
Exemple IP statique :
Exemple IP statique temporaire pour joindre la VM de modèle :
```bash
qm set 9000 --ciuser ansible
@ -566,13 +499,86 @@ qm set 9000 --ipconfig0 ip=192.168.11.150/24,gw=192.168.11.1
qm set 9000 --nameserver 192.168.11.1
```
Pour le modèle, éviter de lui donner une identité finale trop spécifique.
Pour les clones, ces valeurs seront remplacées par l'identité réelle du serveur.
Lidentité définitive doit plutôt être appliquée aux clones.
### Réinitialiser l'état Cloud-Init avant le test
Comme `cloud-init` vient d'être installé après le premier démarrage Debian, nettoyer son état avant de tester l'injection Proxmox :
```bash
cloud-init clean --logs
reboot
```
Au redémarrage, cloud-init doit appliquer le `ciuser`, la clé SSH et les paramètres réseau.
---
## 21. Vérifications avant nettoyage
## 18. SSH et sudo après Cloud-Init
### Tester la connexion par clé
Depuis le poste d'administration :
```bash
ssh -o PreferredAuthentications=publickey ansible@IP_DE_LA_VM
```
Résultat attendu : la connexion fonctionne sans saisir le mot de passe du compte `ansible`.
### Vérifier sudo
Tester :
```bash
sudo -v
```
```bash
sudo -n true && echo OK
```
Le résultat attendu est `OK`. Le flux `make` ne demande pas de mot de passe interactif.
### Correction minimale si sudo manque
Si cloud-init a créé `ansible` sans privilège sudo, corriger seulement ce point depuis la console Proxmox ou un compte administrateur existant :
```bash
usermod -aG sudo ansible
```
Puis retester `sudo -v` avec le compte `ansible`.
Ne pas créer manuellement `/home/ansible/.ssh/authorized_keys` dans le flux normal. Si la clé ne fonctionne pas, corriger les paramètres Cloud-Init dans Proxmox, puis relancer :
```bash
cloud-init clean --logs
reboot
```
---
## 19. SSH durci attendu
Le modèle Chezlepro suppose que Cloud-Init injecte le `ciuser` et sa clé publique avant la prise en charge par Ansible.
L'exigence immédiate est que l'accès par clé fonctionne pour `ansible`.
L'état final sera appliqué et validé par Set-OPS :
```text
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
AuthenticationMethods publickey
```
Ne pas lancer le playbook de préparation tant que l'accès SSH par clé ne fonctionne pas.
---
## 20. Vérifications avant passage à Set-OPS
Redémarrer la VM une dernière fois si nécessaire, puis vérifier :
@ -598,52 +604,71 @@ qemu-guest-agent actif
ssh actif
cloud-init installé
compte ansible fonctionnel
sudo NOPASSWD fonctionnel pour ansible
connexion SSH par clé fonctionnelle
sudo fonctionnel pour ansible
CloudInit Drive présent côté Proxmox
```
---
## 22. Nettoyage avant conversion en template
## 21. Point de bascule vers Set-OPS
Avant de convertir la VM en modèle, nettoyer létat propre à cette installation.
À partir de ce point, ne pas continuer manuellement la configuration du socle. Utiliser le `Makefile` depuis le dépôt Set-OPS.
Exécuter dans la VM :
Depuis le poste Ansible :
```bash
sudo cloud-init clean --logs
sudo apt clean
make preparer-modele
make verifier-modele
```
Nettoyer lidentifiant machine :
Le cleanup final est séparé et protégé :
```bash
sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
make nettoyer-modele CONFIRMER=true
```
Optionnel : vider les journaux persistants si présents :
Ne pas lancer le cleanup final tant que `make verifier-modele` n'a pas réussi.
---
## 22. Dépannage du bootstrap initial
| Symptôme | Cause probable | Correction |
| --- | --- | --- |
| `apt install` demande le DVD | Source APT `cdrom:` encore active. | Refaire la section 13 et relancer `apt update`. |
| `Temporary failure resolving deb.debian.org` | DNS absent ou incorrect. | Vérifier `/etc/resolv.conf`, Proxmox cloud-init DNS, passerelle et réseau. |
| `Network is unreachable` | Pas de route par défaut. | Vérifier l'IP, la passerelle et `ip route`. |
| SSH refuse la clé | Clé Cloud-Init absente, mauvais `ciuser`, ou cloud-init déjà initialisé avant l'ajout des paramètres. | Corriger les paramètres Proxmox, puis `cloud-init clean --logs` et redémarrer. |
| `sudo: a password is required` | Sudo NOPASSWD pas encore posé. | Corriger sudo NOPASSWD pour `ansible`, puis relancer `make preparer-modele`. |
| `ansible is not in the sudoers file` | Le compte a été créé par Cloud-Init sans privilège sudo. | Ajouter seulement `ansible` au groupe `sudo`, puis laisser Set-OPS gérer NOPASSWD. |
| Cloud-init sans effet | `cloud-init` absent au premier boot concerné, disque CloudInit absent, ou VM déjà initialisée. | Installer `cloud-init`, ajouter le CloudInit Drive, corriger les paramètres Proxmox, puis `cloud-init clean --logs` et redémarrer. |
---
## 23. Nettoyage avant conversion en template
Le nettoyage est fait par Set-OPS, pas à la main, sauf diagnostic exceptionnel.
Commande attendue :
```bash
sudo journalctl --rotate
sudo journalctl --vacuum-time=1s
make nettoyer-modele CONFIRMER=true
```
Optionnel : supprimer lhistorique shell du compte courant :
Ce nettoyage peut inclure :
```bash
history -c
```
Puis éteindre :
```bash
sudo shutdown -h now
```text
cloud-init clean --logs
nettoyage du cache APT
rotation ou purge contrôlée des journaux
remise à zéro de /etc/machine-id
suppression des historiques shell
```
---
## 23. Point darrêt : VM prête à convertir
## 24. Point darrêt : VM prête à convertir
À ce stade, la VM doit être arrêtée.
@ -673,8 +698,9 @@ qemu-guest-agent installé
cloud-init installé
cloud-guest-utils installé
sudo installé
compte ansible présent
sudo NOPASSWD pour ansible
compte ansible injecté par cloud-init
clé publique SSH injectée par cloud-init
sudo NOPASSWD pour ansible après préparation Set-OPS
partition EFI 512 Mo
partition / ext4
aucune partition swap
@ -687,7 +713,7 @@ VM éteinte
---
## 24. Conversion en template Proxmox
## 25. Conversion en template Proxmox
Cette commande nest exécutée quaprès validation finale :
@ -705,49 +731,63 @@ qm template 9000
---
## 25. Flux normal après conversion
## 26. Flux normal après conversion
Après conversion du modèle :
Après conversion du modèle, le flux normal passe par `make` et l'API Proxmox.
Les paramètres communs Proxmox sont dans :
```text
1. Cloner le template.
2. Agrandir le disque si nécessaire.
3. Définir hostname, IP, DNS et clé SSH via cloud-init.
inventories/lab/group_vars/proxmox.yml
```
Les secrets d'API doivent être dans un fichier Vault non versionné :
```text
inventories/lab/group_vars/proxmox.vault.yml
```
Créer le clone et l'ajouter à l'inventaire :
```text
1. Cloner le template via l'API Proxmox.
2. Agrandir le disque si demandé.
3. Définir l'identité Cloud-Init.
4. Démarrer la VM.
5. Laisser cloud-init appliquer lidentité initiale.
6. Se connecter en SSH.
7. Appliquer les rôles Ansible depuis Set-OPS.
5. Ajouter l'hôte à l'inventaire Set-OPS.
```
Exemple :
```bash
qm clone 9000 101 --name web-01 --full true
qm resize 101 scsi0 32G
qm set 101 --ciuser ansible
qm set 101 --sshkeys ~/.ssh/id_ed25519.pub
qm set 101 --ipconfig0 ip=192.168.11.101/24,gw=192.168.11.1
qm set 101 --nameserver 192.168.11.1
qm start 101
make creer-vm \
HOTE=web-01 \
VMID=101 \
VLAN=11 \
ADRESSE_IP=192.168.11.101 \
PASSERELLE=192.168.11.1 \
GROUPES="serveurs_debian serveurs_durcis"
```
Puis :
Le VLAN est volontairement saisi au moment de créer chaque VM. Il ne fait pas partie des valeurs persistantes de `make config`.
Après le premier démarrage, tester l'accès :
```bash
ssh ansible@192.168.11.101
```
Ensuite seulement :
Ensuite appliquer la conformité Ansible :
```bash
ansible-playbook -i inventories/production/hosts.yml playbooks/baseline.yml
make deployer HOTE=web-01
```
---
## 26. Résumé court
## 27. Résumé court
Le modèle idéal est simple :
La VM vanille idéale est simple :
```text
Debian 13 minimal
@ -766,11 +806,11 @@ sudo
qemu-guest-agent
cloud-init
cloud-guest-utils
compte ansible
sudo NOPASSWD pour ansible
cloud-init clean
machine-id vidé
VM éteinte
CloudInit Drive Proxmox
ciuser ansible défini dans Proxmox
clé publique SSH définie dans Proxmox
sudo fonctionnel pour ansible
APT réseau fonctionnel
```
La philosophie :

194
docs/vm-lifecycle.md Normal file
View file

@ -0,0 +1,194 @@
# Cycle de vie des VM Chezlepro
Ce document décrit le cycle normal d'une VM Debian Chezlepro, depuis l'installation minimale jusqu'à la conformité continue par Ansible.
## Vue d'ensemble
```text
Debian 13 minimale
→ goldenisation du template
→ cleanup final
→ conversion Proxmox en template
→ clonage
→ identité initiale par cloud-init
→ conformité par groupes Ansible
→ intégrations par rôle
→ conformité continue
```
## 1. VM vanille
La VM vanille est une Debian 13 minimale installée manuellement ou par procédure Proxmox.
Elle contient seulement le nécessaire pour être joignable et prise en charge :
```text
Debian 13
SSH
réseau fonctionnel
cloud-init installé
CloudInit Drive Proxmox présent
utilisateur initial injecté par Cloud-Init
clé publique SSH injectée par Cloud-Init
sudo ou accès root possible
aucun rôle applicatif
aucun secret
```
Cette VM n'est pas encore un golden template Chezlepro.
## 2. Goldenisation
La goldenisation est faite par :
```bash
make preparer-modele
```
Ce playbook ajoute le socle Chezlepro commun au template :
```text
paquets communs
qemu-guest-agent
cloud-init
compte technique Ansible
chrony
SSH socle
durcissement template-safe
nftables préparé mais désactivé
```
Les détails et justifications sont dans :
```text
docs/modeles_vm/debian13-proxmox.md
```
## 3. Validation et cleanup
Après la préparation :
```bash
make verifier-modele
```
Le cleanup final est séparé et protégé :
```bash
make nettoyer-modele CONFIRMER=true
```
Le cleanup ne doit jamais être lancé sur une VM de production.
## 4. Clonage
Le clone reçoit son identité initiale par Proxmox et cloud-init :
```text
hostname
utilisateur initial
clé SSH
IP ou DHCP
passerelle
DNS
agrandissement disque
```
Cloud-init ne remplace pas Ansible pour la configuration réelle du serveur.
## 5. Groupes opérationnels
Une VM déployée doit être placée dans les groupes d'inventaire qui décrivent l'état voulu.
Chaque groupe opérationnel doit avoir un playbook homonyme :
```text
serveurs_debian -> playbooks/groupes/serveurs_debian.yml
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
```
Un groupe sans playbook ne doit pas être assigné à une VM de production tant que son rôle n'est pas implémenté.
## 6. Socle Debian
Le groupe `serveurs_debian` maintient les composants de base :
```bash
make appliquer GROUPE=serveurs_debian
```
## 7. Hardening commun
Le groupe `serveurs_durcis` maintient les couches de sécurité communes :
```bash
make appliquer GROUPE=serveurs_durcis
```
L'accès SSH par mot de passe est désactivé dès le socle. L'activation de nftables doit rester dépendante des règles réseau propres au rôle du serveur.
## 8. Déploiement par Makefile
Pour une VM déjà clonée et démarrée :
```bash
make hote-ajouter HOTE=web-01 ADRESSE_IP=192.168.12.101 GROUPES="serveurs_debian serveurs_durcis"
make deployer HOTE=web-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.
Ensuite, elle lance la vérification post-déploiement :
```text
playbooks/groupes/serveurs_debian.yml
playbooks/groupes/serveurs_durcis.yml
verifier-hote
```
Pour converger un groupe complet :
```bash
make deployer-groupe GROUPE=serveurs_debian
```
## 9. Variables template et conformité
Les variables du template servent à construire le golden template dans `inventories/lab/group_vars/modeles_vm.yml`.
Les variables de conformité servent aux VM déployées dans `inventories/production/group_vars/serveurs_debian.yml`.
Différence attendue :
```text
template : SSH par clé seulement, pare-feu non activé
conformité VM : même base SSH, règles réseau adaptées aux serveurs finaux
```
Ne pas durcir une variable de conformité si son application peut couper l'accès sans validation préalable.
## 10. Intégrations futures
Les intégrations transversales sont ajoutées après socle et durcissement :
```text
DNS interne
PKI interne
identité
supervision
métriques
visualisation
```
Elles sont documentées dans :
```text
docs/integrations-vm.md
```

View file

@ -1,19 +0,0 @@
# Template Debian 13 Proxmox
## Préparation
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_prepare.yml --ask-pass --ask-become-pass
```
## Vérification
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_verify.yml
```
## Nettoyage
```bash
ansible-playbook -i inventories/lab/hosts.yml playbooks/vm_templates/debian13_proxmox_cleanup.yml -e confirm_template_cleanup=true
```

View file

@ -1,119 +1,2 @@
---
chezlepro_timezone: "America/Toronto"
chezlepro_admin_user: "ansible"
# SSH pendant la construction du template.
# Passer à "no" seulement après validation des clés SSH.
chezlepro_ssh_port: 22
chezlepro_ssh_password_authentication: "yes"
chezlepro_ssh_permit_root_login: "no"
chezlepro_ssh_pubkey_authentication: "yes"
chezlepro_ssh_client_alive_interval: 300
chezlepro_ssh_client_alive_count_max: 2
chezlepro_ssh_max_auth_tries: 4
chezlepro_ssh_login_grace_time: "30s"
# systemd-ssh-generator / VSOCK
chezlepro_disable_systemd_ssh_auto: true
# Cloud-init
cloud_init_ssh_pwauth: true
cloud_init_manage_etc_hosts: true
cloud_init_disable_root: true
cloud_init_ssh_deletekeys: true
# Pare-feu : préparé mais non activé dans le template.
chezlepro_nftables_enabled: false
chezlepro_firewall_allow_ssh: true
chezlepro_firewall_allow_icmp: true
chezlepro_firewall_input_policy: "drop"
chezlepro_firewall_forward_policy: "drop"
chezlepro_firewall_output_policy: "accept"
chezlepro_firewall_extra_tcp_ports: []
chezlepro_firewall_extra_udp_ports: []
# Fail2ban
chezlepro_fail2ban_enabled: true
chezlepro_fail2ban_bantime: "1h"
chezlepro_fail2ban_findtime: "10m"
chezlepro_fail2ban_maxretry: 5
# Sécurité
chezlepro_auditd_enabled: true
chezlepro_apparmor_enabled: true
chezlepro_sysctl_apply: true
chezlepro_disable_core_dumps: true
# Unattended upgrades
chezlepro_unattended_upgrades_enabled: true
chezlepro_unattended_remove_unused_dependencies: true
chezlepro_unattended_automatic_reboot: false
# Journald
chezlepro_journald_system_max_use: "200M"
chezlepro_journald_runtime_max_use: "100M"
chezlepro_journald_max_retention_sec: "1month"
# Nettoyage final
confirm_template_cleanup: false
chezlepro_common_packages:
- sudo
- openssh-server
- qemu-guest-agent
- cloud-init
- cloud-guest-utils
- python3
- python3-apt
- python3-pip
- acl
- curl
- wget
- ca-certificates
- gnupg
- git
- vim
- nano
- bash-completion
- tmux
- rsync
- unzip
- zip
- tar
- jq
- htop
- iotop
- iftop
- sysstat
- lsof
- ncdu
- tree
- file
- less
- dnsutils
- iproute2
- iputils-ping
- net-tools
- traceroute
- mtr-tiny
- tcpdump
- netcat-openbsd
- socat
- chrony
- logrotate
- unattended-upgrades
- apt-listchanges
- needrestart
chezlepro_hardening_packages:
- apparmor
- apparmor-utils
- auditd
- audispd-plugins
- fail2ban
- nftables
- unattended-upgrades
- apt-listchanges
- debsums
- needrestart
- libpam-pwquality

View file

@ -0,0 +1,58 @@
---
# Variables de construction du golden template Debian 13 Proxmox.
sudo_ansible_admin_user: "ansible"
# SSH : cloud-init doit avoir injecté ciuser et clé publique avant Ansible.
ssh_baseline_password_authentication: "no"
ssh_baseline_permit_root_login: "no"
ssh_baseline_pubkey_authentication: "yes"
ssh_hardening_port: 22
ssh_hardening_password_authentication: "no"
ssh_hardening_permit_root_login: "no"
ssh_hardening_pubkey_authentication: "yes"
ssh_hardening_client_alive_interval: 300
ssh_hardening_client_alive_count_max: 2
ssh_hardening_max_auth_tries: 3
ssh_hardening_login_grace_time: "20s"
systemd_ssh_auto_disable: true
cloud_init_ssh_pwauth: false
cloud_init_manage_etc_hosts: true
cloud_init_disable_root: true
cloud_init_ssh_deletekeys: true
# Pare-feu : préparé mais non activé dans le template.
nftables_baseline_enabled: false
nftables_baseline_allow_ssh: true
nftables_baseline_allow_icmp: true
nftables_baseline_ssh_port: 22
nftables_baseline_input_policy: "drop"
nftables_baseline_forward_policy: "drop"
nftables_baseline_output_policy: "accept"
nftables_baseline_extra_tcp_ports: []
nftables_baseline_extra_udp_ports: []
fail2ban_ssh_enabled: true
fail2ban_ssh_port: 22
fail2ban_ssh_bantime: "1h"
fail2ban_ssh_findtime: "10m"
fail2ban_ssh_maxretry: 5
auditd_enabled: true
apparmor_enabled: true
sysctl_hardening_apply: true
core_dumps_disable: true
unattended_upgrades_enabled: true
unattended_upgrades_remove_unused_dependencies: true
unattended_upgrades_automatic_reboot: false
journald_system_max_use: "200M"
journald_runtime_max_use: "100M"
journald_max_retention_sec: "1month"
template_cleanup_confirm: false
template_cleanup_admin_user: "ansible"

View file

@ -0,0 +1,8 @@
---
# Copier ce fichier vers proxmox.vault.yml, puis le chiffrer avec :
# ansible-vault encrypt inventories/lab/group_vars/proxmox.vault.yml
#
# Ne jamais versionner proxmox.vault.yml.
proxmox_api_token_id: ""
proxmox_api_token_secret: ""

View file

@ -0,0 +1,20 @@
---
# Parametres non sensibles pour les operations Proxmox.
# Les secrets API doivent aller dans proxmox.vault.yml.
proxmox_api_host: asgard
proxmox_api_user: ansible@pve
proxmox_api_port: '8006'
proxmox_validate_certs: false
proxmox_clone_noeud: asgard
proxmox_clone_vmid_modele: 99999
proxmox_clone_source_nom: modeleChezlepro
proxmox_clone_stockage: TrueNAS
proxmox_clone_format: ''
proxmox_clone_complet: true
proxmox_clone_timeout: 600
proxmox_clone_disque: scsi0
proxmox_clone_interface: net0
proxmox_clone_pont: vmbr1
proxmox_clone_parefeu_interface: false
proxmox_clone_demarrer: true

View file

@ -1,20 +1,26 @@
all:
children:
vm_templates:
modeles_vm:
hosts:
basiqueChezlepro:
ansible_host: 192.168.12.99
ansible_user: ansible
ansible_become: true
web_servers:
serveurs_debian:
hosts: {}
database_servers:
serveurs_durcis:
hosts: {}
monitoring_servers:
serveurs_web:
hosts: {}
proxmox_hosts:
serveurs_bases_donnees:
hosts: {}
serveurs_supervision:
hosts: {}
hotes_proxmox:
hosts: {}

View file

@ -1,2 +1,3 @@
---
# Variables globales de production à remplir progressivement.
# Variables globales de production.
# Les politiques Debian communes sont dans group_vars/serveurs_debian.yml.

View file

@ -0,0 +1,54 @@
---
# Variables de conformité communes aux VM Debian déployées.
sudo_ansible_admin_user: "ansible"
ssh_baseline_password_authentication: "no"
ssh_baseline_permit_root_login: "no"
ssh_baseline_pubkey_authentication: "yes"
ssh_hardening_port: 22
ssh_hardening_password_authentication: "no"
ssh_hardening_permit_root_login: "no"
ssh_hardening_pubkey_authentication: "yes"
ssh_hardening_client_alive_interval: 300
ssh_hardening_client_alive_count_max: 2
ssh_hardening_max_auth_tries: 3
ssh_hardening_login_grace_time: "20s"
systemd_ssh_auto_disable: true
cloud_init_ssh_pwauth: false
cloud_init_manage_etc_hosts: true
cloud_init_disable_root: true
cloud_init_ssh_deletekeys: true
# Pare-feu : ne pas activer globalement sans validation des règles par rôle.
nftables_baseline_enabled: false
nftables_baseline_allow_ssh: true
nftables_baseline_allow_icmp: true
nftables_baseline_ssh_port: 22
nftables_baseline_input_policy: "drop"
nftables_baseline_forward_policy: "drop"
nftables_baseline_output_policy: "accept"
nftables_baseline_extra_tcp_ports: []
nftables_baseline_extra_udp_ports: []
fail2ban_ssh_enabled: true
fail2ban_ssh_port: 22
fail2ban_ssh_bantime: "1h"
fail2ban_ssh_findtime: "10m"
fail2ban_ssh_maxretry: 5
auditd_enabled: true
apparmor_enabled: true
sysctl_hardening_apply: true
core_dumps_disable: true
unattended_upgrades_enabled: true
unattended_upgrades_remove_unused_dependencies: true
unattended_upgrades_automatic_reboot: false
journald_system_max_use: "200M"
journald_runtime_max_use: "100M"
journald_max_retention_sec: "1month"

View file

@ -1,12 +1,28 @@
all:
children:
vm_templates:
modeles_vm:
hosts: {}
web_servers:
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
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
serveurs_web:
hosts: {}
database_servers:
serveurs_bases_donnees:
hosts: {}
monitoring_servers:
serveurs_supervision:
hosts: {}
proxmox_hosts:
hotes_proxmox:
hosts: {}

View file

@ -1,10 +0,0 @@
---
- name: Appliquer le socle Debian commun
hosts: all
become: true
gather_facts: true
roles:
- common_packages
- chrony
- ssh_baseline

View file

@ -0,0 +1,22 @@
---
- name: Appliquer le groupe serveurs_debian
hosts: serveurs_debian
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- common_packages
- qemu_guest_agent
- cloud_init
- sudo_ansible
- chrony
- ssh_baseline
- systemd_ssh_auto
- motd

View file

@ -1,6 +1,6 @@
---
- name: Appliquer le hardening Debian serveur Chezlepro
hosts: all
- name: Appliquer le groupe serveurs_durcis
hosts: serveurs_durcis
become: true
gather_facts: true

View file

@ -0,0 +1,38 @@
---
- name: Vérifier une VM Debian gérée par Set-OPS
hosts: serveurs_debian
become: true
gather_facts: true
tasks:
- name: Vérifier que la cible est Debian 13
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
- ansible_facts.distribution_major_version == "13"
fail_msg: "Cette vérification attend une VM Debian 13."
- name: Collecter les services
ansible.builtin.service_facts:
- name: Vérifier les services du socle
ansible.builtin.assert:
that:
- '"ssh.service" in ansible_facts.services'
- '"qemu-guest-agent.service" in ansible_facts.services'
- '"chrony.service" in ansible_facts.services'
- ansible_facts.services["ssh.service"].state == "running"
- ansible_facts.services["qemu-guest-agent.service"].state == "running"
- ansible_facts.services["chrony.service"].state == "running"
fail_msg: "Un service du socle Debian n'est pas actif."
- name: Vérifier sudo non interactif via become
ansible.builtin.command: whoami
changed_when: false
register: verifier_hote_debian_whoami
- name: Valider l'élévation root
ansible.builtin.assert:
that:
- verifier_hote_debian_whoami.stdout == "root"
fail_msg: "L'élévation sudo ne retourne pas root."

View file

@ -1,6 +1,6 @@
---
- name: Nettoyage final avant conversion en template Proxmox
hosts: vm_templates
hosts: modeles_vm
become: true
gather_facts: true

View file

@ -1,6 +1,6 @@
---
- name: Préparer le template Debian 13 Proxmox Chezlepro
hosts: vm_templates
hosts: modeles_vm
become: true
gather_facts: true

View file

@ -0,0 +1,130 @@
---
- name: Vérifier le template Debian 13 Proxmox Chezlepro
hosts: modeles_vm
become: true
gather_facts: true
tasks:
- name: Vérifier que la cible est Debian 13
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
- ansible_facts.distribution_major_version == "13"
fail_msg: "Ce template doit être une Debian 13."
- name: Afficher les partitions
ansible.builtin.command: lsblk -f
register: lsblk_output
changed_when: false
- name: Résultat lsblk
ansible.builtin.debug:
var: lsblk_output.stdout_lines
- name: Vérifier l'absence de swap actif
ansible.builtin.command: swapon --show
register: swapon_output
changed_when: false
failed_when: swapon_output.stdout | length > 0
- name: Collecter les paquets installés
ansible.builtin.package_facts:
manager: auto
- name: Valider les paquets requis du template
ansible.builtin.assert:
that:
- "'sudo' in ansible_facts.packages"
- "'openssh-server' in ansible_facts.packages"
- "'qemu-guest-agent' in ansible_facts.packages"
- "'cloud-init' in ansible_facts.packages"
- "'cloud-guest-utils' in ansible_facts.packages"
- "'chrony' in ansible_facts.packages"
- "'apparmor' in ansible_facts.packages"
- "'auditd' in ansible_facts.packages"
- "'fail2ban' in ansible_facts.packages"
- "'nftables' in ansible_facts.packages"
- "'unattended-upgrades' in ansible_facts.packages"
fail_msg: "Un paquet requis du golden template est absent."
- name: Vérifier l'absence d'environnement graphique
ansible.builtin.assert:
that:
- "'task-desktop' not in ansible_facts.packages"
- "'xserver-xorg' not in ansible_facts.packages"
- "'gdm3' not in ansible_facts.packages"
- "'sddm' not in ansible_facts.packages"
- "'lightdm' not in ansible_facts.packages"
fail_msg: "Un environnement graphique semble installé."
- name: Collecter les services
ansible.builtin.service_facts:
- name: Valider les services de base
ansible.builtin.assert:
that:
- ansible_facts.services['ssh.service'].state == 'running'
- ansible_facts.services['qemu-guest-agent.service'].state == 'running'
- ansible_facts.services['chrony.service'].state == 'running'
- ansible_facts.services['auditd.service'].state == 'running'
- ansible_facts.services['fail2ban.service'].state == 'running'
fail_msg: "Un service de base n'est pas actif."
- name: Vérifier que AppArmor est actif
ansible.builtin.command: aa-enabled
register: apparmor_state
changed_when: false
- name: Valider l'état nftables du template
ansible.builtin.assert:
that:
- ansible_facts.services['nftables.service'].state != 'running'
fail_msg: "nftables ne doit pas être actif par défaut dans le template."
- name: Vérifier cloud-init
ansible.builtin.command: cloud-init --version
register: cloud_init_version
changed_when: false
- name: Afficher cloud-init
ansible.builtin.debug:
var: cloud_init_version.stdout
- name: Vérifier sudo NOPASSWD du compte Ansible
ansible.builtin.command: sudo -n true
become: false
changed_when: false
- name: Collecter la configuration SSH effective
ansible.builtin.command: sshd -T
register: sshd_effective_config
changed_when: false
- name: Valider le hardening SSH effectif
ansible.builtin.assert:
that:
- "'passwordauthentication no' in sshd_effective_config.stdout"
- "'kbdinteractiveauthentication no' in sshd_effective_config.stdout"
- "'permitrootlogin no' in sshd_effective_config.stdout"
- "'pubkeyauthentication yes' in sshd_effective_config.stdout"
- "'authenticationmethods publickey' in sshd_effective_config.stdout"
- "'x11forwarding no' in sshd_effective_config.stdout"
- "'allowtcpforwarding no' in sshd_effective_config.stdout"
- "'allowagentforwarding no' in sshd_effective_config.stdout"
- "'allowstreamlocalforwarding no' in sshd_effective_config.stdout"
- "'permittunnel no' in sshd_effective_config.stdout"
- "'permituserenvironment no' in sshd_effective_config.stdout"
- "'maxauthtries 3' in sshd_effective_config.stdout"
- "'logingracetime 20' in sshd_effective_config.stdout"
- "'maxsessions 2' in sshd_effective_config.stdout"
fail_msg: "La configuration SSH effective ne respecte pas le hardening attendu."
- name: Vérifier les erreurs critiques du boot courant
ansible.builtin.command: journalctl -b -p err..alert --no-pager
register: boot_errors
changed_when: false
failed_when: false
- name: Afficher les erreurs du boot courant
ansible.builtin.debug:
var: boot_errors.stdout_lines

View file

@ -1,3 +1,79 @@
# Playbooks Proxmox
Espace réservé aux futurs playbooks.
Ces playbooks pilotent Proxmox par API depuis le poste Ansible.
Les valeurs Cloud-Init communes déjà définies sur le modèle Proxmox sont héritées par les clones. Set-OPS ne les redemande pas.
## Prérequis du poste Ansible
Le clonage Proxmox utilise les modules `community.general.proxmox_*`.
Le poste Ansible doit donc avoir :
- la collection Ansible `community.general` ;
- la bibliothèque Python `proxmoxer` disponible dans le Python utilisé par Ansible.
Sur Debian, installer généralement :
```bash
sudo apt install python3-proxmoxer
```
Vérification locale :
```bash
python3 -c 'import proxmoxer'
```
Les paramètres non sensibles sont dans :
```text
inventories/lab/group_vars/proxmox.yml
```
Assistant terminal :
```bash
make config
```
Les secrets d'API doivent être dans un fichier Vault non versionné :
```text
inventories/lab/group_vars/proxmox.vault.yml
```
Créer le fichier depuis l'exemple, puis le chiffrer :
```bash
cp inventories/lab/group_vars/proxmox.vault.yml.example inventories/lab/group_vars/proxmox.vault.yml
ansible-vault encrypt inventories/lab/group_vars/proxmox.vault.yml
```
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 \
GROUPES="serveurs_debian serveurs_durcis"
```
`VLAN` doit être saisi à chaque création de VM pour éviter de réutiliser par erreur un réseau d'un autre clone.
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"
```
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
```

View file

@ -0,0 +1,202 @@
---
- name: Cloner une VM Debian depuis le modele Proxmox
hosts: localhost
connection: local
gather_facts: false
become: false
tasks:
- name: Verifier les fichiers de variables Proxmox optionnels
ansible.builtin.stat:
path: "{{ item }}"
loop:
- "{{ playbook_dir }}/../../inventories/lab/group_vars/proxmox.yml"
- "{{ playbook_dir }}/../../inventories/lab/group_vars/proxmox.vault.yml"
- "{{ playbook_dir }}/../../inventories/lab/group_vars/proxmox.local.yml"
register: proxmox_fichiers_variables
- name: Charger les variables Proxmox non sensibles disponibles
ansible.builtin.include_vars:
file: "{{ item.stat.path }}"
loop: "{{ proxmox_fichiers_variables.results }}"
when:
- item.stat.exists
- item.stat.path is not search('vault')
- name: Charger les secrets Proxmox Vault
block:
- name: Charger proxmox.vault.yml
ansible.builtin.include_vars:
file: "{{ item.stat.path }}"
loop: "{{ proxmox_fichiers_variables.results }}"
when:
- item.stat.exists
- item.stat.path is search('vault')
no_log: true
rescue:
- name: Expliquer l echec de chargement du Vault
ansible.builtin.fail:
msg: >-
Impossible de charger inventories/lab/group_vars/proxmox.vault.yml.
Si le fichier est chiffre avec Ansible Vault, fournir son mot de passe
ou definir ANSIBLE_VAULT_PASSWORD_FILE avant de relancer make creer-vm.
- name: Appliquer les valeurs Proxmox avec repli environnement
ansible.builtin.set_fact:
proxmox_api_host_effectif: "{{ proxmox_api_host | default(lookup('env', 'PROXMOX_API_HOST'), true) }}"
proxmox_api_user_effectif: "{{ proxmox_api_user | default(lookup('env', 'PROXMOX_API_USER'), true) }}"
proxmox_api_token_id_effectif: "{{ proxmox_api_token_id | default(lookup('env', 'PROXMOX_API_TOKEN_ID'), true) }}"
proxmox_api_token_secret_effectif: "{{ proxmox_api_token_secret | default(lookup('env', 'PROXMOX_API_TOKEN_SECRET'), true) }}"
proxmox_api_port_effectif: "{{ proxmox_api_port | default(lookup('env', 'PROXMOX_API_PORT'), true) }}"
no_log: true
- name: Valider les parametres obligatoires Proxmox
ansible.builtin.assert:
that:
- proxmox_api_host_effectif | length > 0
- proxmox_api_user_effectif | length > 0
- proxmox_api_token_id_effectif | length > 0
- proxmox_api_token_secret_effectif | length > 0
- proxmox_clone_nom is defined
- proxmox_clone_nom | length > 0
- proxmox_clone_vmid_modele is defined
- proxmox_clone_vmid is defined
- proxmox_clone_noeud is defined
- proxmox_clone_noeud | length > 0
- proxmox_clone_ipconfig0 is defined
- proxmox_clone_ipconfig0 | length > 0
fail_msg: "Parametres Proxmox incomplets. Verifier proxmox.yml, proxmox.vault.yml et les variables make propres a la VM."
- name: Lire la cle publique SSH depuis le fichier fourni
ansible.builtin.set_fact:
proxmox_clone_sshkeys: "{{ lookup('ansible.builtin.file', proxmox_clone_cle_publique_fichier) }}"
when:
- proxmox_clone_cle_publique is not defined
- proxmox_clone_cle_publique_fichier is defined
- proxmox_clone_cle_publique_fichier | length > 0
- name: Utiliser la cle publique SSH fournie directement
ansible.builtin.set_fact:
proxmox_clone_sshkeys: "{{ proxmox_clone_cle_publique }}"
when:
- proxmox_clone_cle_publique is defined
- proxmox_clone_cle_publique | length > 0
- name: Construire les serveurs DNS Cloud-Init
ansible.builtin.set_fact:
proxmox_clone_nameservers: >-
{{
(proxmox_clone_dns | default('') | split(','))
| map('trim')
| reject('equalto', '')
| list
}}
- name: Verifier la bibliotheque Python proxmoxer locale
ansible.builtin.command:
cmd: "{{ ansible_playbook_python }} -c 'import proxmoxer'"
changed_when: false
failed_when: false
register: proxmoxer_verification
- name: Refuser si proxmoxer est absent
ansible.builtin.fail:
msg: >-
La bibliotheque Python proxmoxer est absente pour {{ ansible_playbook_python }}.
Installer le paquet systeme approprie, par exemple
sudo apt install python3-proxmoxer, ou installer proxmoxer dans
l'environnement Python utilise par Ansible, puis relancer make creer-vm.
when: proxmoxer_verification.rc != 0
- name: Cloner la VM depuis le modele
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
clone: "{{ proxmox_clone_source_nom | default('modele-debian13') }}"
vmid: "{{ proxmox_clone_vmid_modele | int }}"
newid: "{{ proxmox_clone_vmid | int }}"
name: "{{ proxmox_clone_nom }}"
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
format: "{{ proxmox_clone_format | default(omit, true) }}"
full: "{{ proxmox_clone_complet | default(true) | bool }}"
timeout: "{{ proxmox_clone_timeout | default(600) | int }}"
- name: Ajuster le reseau de la VM clonee
community.general.proxmox_nic:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
vmid: "{{ proxmox_clone_vmid | int }}"
interface: "{{ proxmox_clone_interface | default('net0') }}"
model: virtio
bridge: "{{ proxmox_clone_pont }}"
tag: "{{ proxmox_clone_vlan | int if proxmox_clone_vlan is defined and proxmox_clone_vlan | string | length > 0 else omit }}"
firewall: "{{ proxmox_clone_parefeu_interface | default(false) | bool }}"
state: present
when:
- proxmox_clone_pont is defined
- proxmox_clone_pont | length > 0
- name: Agrandir le disque principal du clone
community.general.proxmox_disk:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
vmid: "{{ proxmox_clone_vmid | int }}"
disk: "{{ proxmox_clone_disque | default('scsi0') }}"
size: "{{ proxmox_clone_taille_disque }}"
state: resized
when:
- proxmox_clone_taille_disque is defined
- proxmox_clone_taille_disque | length > 0
- name: Configurer Cloud-Init sur le clone
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
vmid: "{{ proxmox_clone_vmid | int }}"
name: "{{ proxmox_clone_nom }}"
update: true
ciuser: "{{ proxmox_clone_ciuser | default(omit, true) }}"
sshkeys: "{{ proxmox_clone_sshkeys | default(omit) }}"
ipconfig:
ipconfig0: "{{ proxmox_clone_ipconfig0 }}"
nameservers: "{{ proxmox_clone_nameservers if proxmox_clone_nameservers | length > 0 else omit }}"
searchdomains: "{{ proxmox_clone_domaines_recherche | default(omit, true) }}"
agent: "enabled=1"
- name: Demarrer le clone
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
vmid: "{{ proxmox_clone_vmid | int }}"
name: "{{ proxmox_clone_nom }}"
state: started
when: proxmox_clone_demarrer | default(true) | bool
- name: Afficher le resume du clone
ansible.builtin.debug:
msg:
- "VM creee: {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})"
- "Cloud-Init ipconfig0: {{ proxmox_clone_ipconfig0 }}"

View file

@ -1,51 +0,0 @@
---
- name: Vérifier le template Debian 13 Proxmox Chezlepro
hosts: vm_templates
become: true
gather_facts: true
tasks:
- name: Afficher les partitions
ansible.builtin.command: lsblk -f
register: lsblk_output
changed_when: false
- name: Résultat lsblk
ansible.builtin.debug:
var: lsblk_output.stdout_lines
- name: Vérifier l'absence de swap actif
ansible.builtin.command: swapon --show
register: swapon_output
changed_when: false
failed_when: swapon_output.stdout | length > 0
- name: Collecter les services
ansible.builtin.service_facts:
- name: Valider les services de base
ansible.builtin.assert:
that:
- ansible_facts.services['ssh.service'].state == 'running'
- ansible_facts.services['qemu-guest-agent.service'].state == 'running'
- ansible_facts.services['chrony.service'].state == 'running'
fail_msg: "Un service de base n'est pas actif."
- name: Vérifier cloud-init
ansible.builtin.command: cloud-init --version
register: cloud_init_version
changed_when: false
- name: Afficher cloud-init
ansible.builtin.debug:
var: cloud_init_version.stdout
- name: Vérifier les erreurs critiques du boot courant
ansible.builtin.command: journalctl -b -p err..alert --no-pager
register: boot_errors
changed_when: false
failed_when: false
- name: Afficher les erreurs du boot courant
ansible.builtin.debug:
var: boot_errors.stdout_lines

View file

@ -1,2 +1,2 @@
---
chezlepro_apparmor_enabled: true
apparmor_enabled: true

View file

@ -11,5 +11,5 @@
name: apparmor
enabled: true
state: started
when: chezlepro_apparmor_enabled | bool
when: apparmor_enabled | bool
failed_when: false

View file

@ -1,2 +1,2 @@
---
chezlepro_auditd_enabled: true
auditd_enabled: true

View file

@ -14,12 +14,12 @@
group: root
mode: "0640"
notify: Restart auditd
when: chezlepro_auditd_enabled | bool
when: auditd_enabled | bool
- name: Activer auditd
ansible.builtin.systemd:
name: auditd
enabled: true
state: started
when: chezlepro_auditd_enabled | bool
when: auditd_enabled | bool
failed_when: false

View file

@ -1,11 +0,0 @@
---
- name: Installer chrony
ansible.builtin.apt:
name: chrony
state: present
- name: Activer et démarrer chrony
ansible.builtin.systemd:
name: chrony
enabled: true
state: started

View file

@ -1,5 +0,0 @@
---
cloud_init_ssh_pwauth: true
cloud_init_manage_etc_hosts: true
cloud_init_disable_root: true
cloud_init_ssh_deletekeys: true

View file

@ -1,32 +0,0 @@
---
- name: Installer cloud-init et cloud-guest-utils
ansible.builtin.apt:
name:
- cloud-init
- cloud-guest-utils
state: present
- name: Déployer la configuration cloud-init Chezlepro
ansible.builtin.template:
src: 99_chezlepro.cfg.j2
dest: /etc/cloud/cloud.cfg.d/99_chezlepro.cfg
owner: root
group: root
mode: "0644"
- name: Activer les services cloud-init Debian 13
ansible.builtin.systemd:
name: "{{ item }}"
enabled: true
loop:
- cloud-init-local.service
- cloud-init-main.service
- cloud-init-network.service
- cloud-config.service
- cloud-final.service
failed_when: false
- name: Vérifier cloud-init
ansible.builtin.command: cloud-init --version
register: cloud_init_version
changed_when: false

View file

@ -1,23 +0,0 @@
# Managed by Ansible — Set-OPS
# Configuration cloud-init pour template Debian 13 Chezlepro.
datasource_list: [ NoCloud, ConfigDrive, None ]
preserve_hostname: false
manage_etc_hosts: {{ cloud_init_manage_etc_hosts | bool | lower }}
disable_root: {{ cloud_init_disable_root | bool | lower }}
ssh_pwauth: {{ cloud_init_ssh_pwauth | bool | lower }}
ssh_deletekeys: {{ cloud_init_ssh_deletekeys | bool | lower }}
ssh_genkeytypes:
- rsa
- ecdsa
- ed25519
growpart:
mode: auto
devices:
- /
ignore_growroot_disabled: false
resize_rootfs: true

View file

@ -1,2 +0,0 @@
---
chezlepro_common_packages: []

View file

@ -1,18 +0,0 @@
---
- name: Mettre à jour le cache APT
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Appliquer les mises à jour disponibles
ansible.builtin.apt:
upgrade: full
- name: Installer les paquets communs
ansible.builtin.apt:
name: "{{ chezlepro_common_packages }}"
state: present
- name: Supprimer les dépendances devenues inutiles
ansible.builtin.apt:
autoremove: true

View file

@ -1,8 +0,0 @@
---
- name: Déployer le MOTD Chezlepro
ansible.builtin.template:
src: motd.j2
dest: /etc/motd
owner: root
group: root
mode: "0644"

View file

@ -1,2 +0,0 @@
Système Chezlepro
Gestion : Set-OPS / Ansible

View file

@ -1,11 +0,0 @@
---
- name: Installer qemu-guest-agent
ansible.builtin.apt:
name: qemu-guest-agent
state: present
- name: Activer et démarrer qemu-guest-agent
ansible.builtin.systemd:
name: qemu-guest-agent
enabled: true
state: started

View file

@ -1,5 +1,5 @@
---
cloud_init_ssh_pwauth: true
cloud_init_ssh_pwauth: false
cloud_init_manage_etc_hosts: true
cloud_init_disable_root: true
cloud_init_ssh_deletekeys: true

View file

@ -1,2 +1,48 @@
---
chezlepro_common_packages: []
common_packages_list:
- sudo
- openssh-server
- qemu-guest-agent
- cloud-init
- cloud-guest-utils
- python3
- python3-apt
- python3-pip
- acl
- curl
- wget
- ca-certificates
- gnupg
- git
- vim
- nano
- bash-completion
- tmux
- rsync
- unzip
- zip
- tar
- jq
- htop
- iotop
- iftop
- sysstat
- lsof
- ncdu
- tree
- file
- less
- dnsutils
- iproute2
- iputils-ping
- net-tools
- traceroute
- mtr-tiny
- tcpdump
- netcat-openbsd
- socat
- chrony
- logrotate
- unattended-upgrades
- apt-listchanges
- needrestart

View file

@ -10,7 +10,7 @@
- name: Installer les paquets communs
ansible.builtin.apt:
name: "{{ chezlepro_common_packages }}"
name: "{{ common_packages_list }}"
state: present
- name: Supprimer les dépendances devenues inutiles

View file

@ -1,2 +1,2 @@
---
chezlepro_disable_core_dumps: true
core_dumps_disable: true

View file

@ -6,7 +6,7 @@
owner: root
group: root
mode: "0644"
when: chezlepro_disable_core_dumps | bool
when: core_dumps_disable | bool
- name: Configurer systemd-coredump si présent
ansible.builtin.template:
@ -15,5 +15,5 @@
owner: root
group: root
mode: "0644"
when: chezlepro_disable_core_dumps | bool
when: core_dumps_disable | bool
failed_when: false

View file

@ -1,6 +1,6 @@
---
chezlepro_fail2ban_enabled: true
chezlepro_fail2ban_bantime: "1h"
chezlepro_fail2ban_findtime: "10m"
chezlepro_fail2ban_maxretry: 5
chezlepro_ssh_port: 22
fail2ban_ssh_enabled: true
fail2ban_ssh_bantime: "1h"
fail2ban_ssh_findtime: "10m"
fail2ban_ssh_maxretry: 5
fail2ban_ssh_port: 22

View file

@ -26,4 +26,4 @@
name: fail2ban
enabled: true
state: started
when: chezlepro_fail2ban_enabled | bool
when: fail2ban_ssh_enabled | bool

View file

@ -1,9 +1,9 @@
# Managed by Ansible — Set-OPS
[sshd]
enabled = {{ 'true' if chezlepro_fail2ban_enabled | bool else 'false' }}
port = {{ chezlepro_ssh_port }}
enabled = {{ 'true' if fail2ban_ssh_enabled | bool else 'false' }}
port = {{ fail2ban_ssh_port }}
backend = systemd
maxretry = {{ chezlepro_fail2ban_maxretry }}
findtime = {{ chezlepro_fail2ban_findtime }}
bantime = {{ chezlepro_fail2ban_bantime }}
maxretry = {{ fail2ban_ssh_maxretry }}
findtime = {{ fail2ban_ssh_findtime }}
bantime = {{ fail2ban_ssh_bantime }}

View file

@ -1,2 +1,13 @@
---
chezlepro_hardening_packages: []
hardening_packages_list:
- apparmor
- apparmor-utils
- auditd
- audispd-plugins
- fail2ban
- nftables
- unattended-upgrades
- apt-listchanges
- debsums
- needrestart
- libpam-pwquality

View file

@ -1,7 +1,7 @@
---
- name: Installer les paquets de hardening
ansible.builtin.apt:
name: "{{ chezlepro_hardening_packages }}"
name: "{{ hardening_packages_list }}"
state: present
update_cache: true
cache_valid_time: 3600

View file

@ -1,4 +1,4 @@
---
chezlepro_journald_system_max_use: "200M"
chezlepro_journald_runtime_max_use: "100M"
chezlepro_journald_max_retention_sec: "1month"
journald_system_max_use: "200M"
journald_runtime_max_use: "100M"
journald_max_retention_sec: "1month"

View file

@ -1,7 +1,7 @@
# Managed by Ansible — Set-OPS
[Journal]
SystemMaxUse={{ chezlepro_journald_system_max_use }}
RuntimeMaxUse={{ chezlepro_journald_runtime_max_use }}
MaxRetentionSec={{ chezlepro_journald_max_retention_sec }}
SystemMaxUse={{ journald_system_max_use }}
RuntimeMaxUse={{ journald_runtime_max_use }}
MaxRetentionSec={{ journald_max_retention_sec }}
Compress=yes
Seal=yes

View file

@ -1,10 +1,10 @@
---
chezlepro_nftables_enabled: false
chezlepro_firewall_allow_ssh: true
chezlepro_firewall_allow_icmp: true
chezlepro_firewall_input_policy: "drop"
chezlepro_firewall_forward_policy: "drop"
chezlepro_firewall_output_policy: "accept"
chezlepro_ssh_port: 22
chezlepro_firewall_extra_tcp_ports: []
chezlepro_firewall_extra_udp_ports: []
nftables_baseline_enabled: false
nftables_baseline_allow_ssh: true
nftables_baseline_allow_icmp: true
nftables_baseline_input_policy: "drop"
nftables_baseline_forward_policy: "drop"
nftables_baseline_output_policy: "accept"
nftables_baseline_ssh_port: 22
nftables_baseline_extra_tcp_ports: []
nftables_baseline_extra_udp_ports: []

View file

@ -12,19 +12,19 @@
group: root
mode: "0644"
notify: Reload nftables
when: chezlepro_nftables_enabled | bool
when: nftables_baseline_enabled | bool
- name: Activer nftables si demandé
ansible.builtin.systemd:
name: nftables
enabled: true
state: started
when: chezlepro_nftables_enabled | bool
when: nftables_baseline_enabled | bool
- name: Désactiver nftables si demandé
ansible.builtin.systemd:
name: nftables
enabled: false
state: stopped
when: not (chezlepro_nftables_enabled | bool)
when: not (nftables_baseline_enabled | bool)
failed_when: false

View file

@ -5,35 +5,35 @@ flush ruleset
table inet chezlepro_filter {
chain input {
type filter hook input priority 0; policy {{ chezlepro_firewall_input_policy }};
type filter hook input priority 0; policy {{ nftables_baseline_input_policy }};
iif "lo" accept
ct state established,related accept
ct state invalid drop
{% if chezlepro_firewall_allow_icmp | bool %}
{% if nftables_baseline_allow_icmp | bool %}
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
{% endif %}
{% if chezlepro_firewall_allow_ssh | bool %}
tcp dport {{ chezlepro_ssh_port }} accept
{% if nftables_baseline_allow_ssh | bool %}
tcp dport {{ nftables_baseline_ssh_port }} accept
{% endif %}
{% for port in chezlepro_firewall_extra_tcp_ports %}
{% for port in nftables_baseline_extra_tcp_ports %}
tcp dport {{ port }} accept
{% endfor %}
{% for port in chezlepro_firewall_extra_udp_ports %}
{% for port in nftables_baseline_extra_udp_ports %}
udp dport {{ port }} accept
{% endfor %}
}
chain forward {
type filter hook forward priority 0; policy {{ chezlepro_firewall_forward_policy }};
type filter hook forward priority 0; policy {{ nftables_baseline_forward_policy }};
}
chain output {
type filter hook output priority 0; policy {{ chezlepro_firewall_output_policy }};
type filter hook output priority 0; policy {{ nftables_baseline_output_policy }};
}
}

View file

@ -1,4 +0,0 @@
---
chezlepro_ssh_password_authentication: "yes"
chezlepro_ssh_permit_root_login: "no"
chezlepro_ssh_pubkey_authentication: "yes"

View file

@ -1,11 +0,0 @@
---
- name: Validate and reload ssh
block:
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded

View file

@ -1,28 +0,0 @@
---
- name: Installer openssh-server
ansible.builtin.apt:
name: openssh-server
state: present
- name: Créer le répertoire sshd_config.d
ansible.builtin.file:
path: /etc/ssh/sshd_config.d
state: directory
owner: root
group: root
mode: "0755"
- name: Déployer la configuration SSH Chezlepro
ansible.builtin.template:
src: 10-chezlepro.conf.j2
dest: /etc/ssh/sshd_config.d/10-chezlepro.conf
owner: root
group: root
mode: "0644"
notify: Validate and reload ssh
- name: Activer et démarrer SSH
ansible.builtin.systemd:
name: ssh
enabled: true
state: started

View file

@ -1,8 +0,0 @@
# Managed by Ansible — Set-OPS
PermitRootLogin {{ chezlepro_ssh_permit_root_login }}
PubkeyAuthentication {{ chezlepro_ssh_pubkey_authentication }}
PasswordAuthentication {{ chezlepro_ssh_password_authentication }}
KbdInteractiveAuthentication no
X11Forwarding no
UsePAM yes

View file

@ -1,2 +0,0 @@
---
chezlepro_admin_user: ansible

View file

@ -1,23 +0,0 @@
---
- name: S'assurer que le groupe sudo existe
ansible.builtin.group:
name: sudo
state: present
- name: S'assurer que le compte technique existe
ansible.builtin.user:
name: "{{ chezlepro_admin_user }}"
shell: /bin/bash
groups: sudo
append: true
create_home: true
state: present
- name: Déployer sudo NOPASSWD pour le compte technique
ansible.builtin.template:
src: 90-ansible.j2
dest: "/etc/sudoers.d/90-{{ chezlepro_admin_user }}"
owner: root
group: root
mode: "0440"
validate: "visudo -cf %s"

View file

@ -1,2 +0,0 @@
# Managed by Ansible — Set-OPS
{{ chezlepro_admin_user }} ALL=(ALL) NOPASSWD:ALL

View file

@ -1,4 +1,4 @@
---
chezlepro_ssh_password_authentication: "yes"
chezlepro_ssh_permit_root_login: "no"
chezlepro_ssh_pubkey_authentication: "yes"
ssh_baseline_password_authentication: "no"
ssh_baseline_permit_root_login: "no"
ssh_baseline_pubkey_authentication: "yes"

View file

@ -1,11 +1,11 @@
---
- name: Validate and reload ssh
block:
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
listen: Validate and reload ssh
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
listen: Validate and reload ssh

View file

@ -1,8 +1,14 @@
# Managed by Ansible — Set-OPS
PermitRootLogin {{ chezlepro_ssh_permit_root_login }}
PubkeyAuthentication {{ chezlepro_ssh_pubkey_authentication }}
PasswordAuthentication {{ chezlepro_ssh_password_authentication }}
PermitRootLogin {{ ssh_baseline_permit_root_login }}
PubkeyAuthentication {{ ssh_baseline_pubkey_authentication }}
PasswordAuthentication {{ ssh_baseline_password_authentication }}
AuthenticationMethods publickey
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitEmptyPasswords no
HostbasedAuthentication no
IgnoreRhosts yes
X11Forwarding no
PermitUserEnvironment no
UsePAM yes

View file

@ -1,9 +1,9 @@
---
chezlepro_ssh_port: 22
chezlepro_ssh_password_authentication: "yes"
chezlepro_ssh_permit_root_login: "no"
chezlepro_ssh_pubkey_authentication: "yes"
chezlepro_ssh_client_alive_interval: 300
chezlepro_ssh_client_alive_count_max: 2
chezlepro_ssh_max_auth_tries: 4
chezlepro_ssh_login_grace_time: "30s"
ssh_hardening_port: 22
ssh_hardening_password_authentication: "no"
ssh_hardening_permit_root_login: "no"
ssh_hardening_pubkey_authentication: "yes"
ssh_hardening_client_alive_interval: 300
ssh_hardening_client_alive_count_max: 2
ssh_hardening_max_auth_tries: 3
ssh_hardening_login_grace_time: "20s"

View file

@ -1,11 +1,11 @@
---
- name: Validate and reload ssh
block:
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
listen: Validate and reload ssh
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
listen: Validate and reload ssh

View file

@ -1,15 +1,27 @@
# Managed by Ansible — Set-OPS
Port {{ chezlepro_ssh_port }}
PermitRootLogin {{ chezlepro_ssh_permit_root_login }}
PubkeyAuthentication {{ chezlepro_ssh_pubkey_authentication }}
PasswordAuthentication {{ chezlepro_ssh_password_authentication }}
Port {{ ssh_hardening_port }}
PermitRootLogin {{ ssh_hardening_permit_root_login }}
PubkeyAuthentication {{ ssh_hardening_pubkey_authentication }}
PasswordAuthentication {{ ssh_hardening_password_authentication }}
AuthenticationMethods publickey
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitEmptyPasswords no
HostbasedAuthentication no
IgnoreRhosts yes
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
ClientAliveInterval {{ chezlepro_ssh_client_alive_interval }}
ClientAliveCountMax {{ chezlepro_ssh_client_alive_count_max }}
MaxAuthTries {{ chezlepro_ssh_max_auth_tries }}
LoginGraceTime {{ chezlepro_ssh_login_grace_time }}
AllowStreamLocalForwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
PermitTTY yes
TCPKeepAlive no
ClientAliveInterval {{ ssh_hardening_client_alive_interval }}
ClientAliveCountMax {{ ssh_hardening_client_alive_count_max }}
MaxAuthTries {{ ssh_hardening_max_auth_tries }}
LoginGraceTime {{ ssh_hardening_login_grace_time }}
MaxSessions 2
MaxStartups 5:30:20

View file

@ -1,2 +1,2 @@
---
chezlepro_admin_user: ansible
sudo_ansible_admin_user: ansible

View file

@ -6,7 +6,7 @@
- name: S'assurer que le compte technique existe
ansible.builtin.user:
name: "{{ chezlepro_admin_user }}"
name: "{{ sudo_ansible_admin_user }}"
shell: /bin/bash
groups: sudo
append: true
@ -16,7 +16,7 @@
- name: Déployer sudo NOPASSWD pour le compte technique
ansible.builtin.template:
src: 90-ansible.j2
dest: "/etc/sudoers.d/90-{{ chezlepro_admin_user }}"
dest: "/etc/sudoers.d/90-{{ sudo_ansible_admin_user }}"
owner: root
group: root
mode: "0440"

View file

@ -1,2 +1,2 @@
# Managed by Ansible — Set-OPS
{{ chezlepro_admin_user }} ALL=(ALL) NOPASSWD:ALL
{{ sudo_ansible_admin_user }} ALL=(ALL) NOPASSWD:ALL

View file

@ -1,6 +1,6 @@
---
chezlepro_sysctl_apply: true
chezlepro_sysctl_hardening_values:
sysctl_hardening_apply: true
sysctl_hardening_values:
fs.protected_hardlinks: 1
fs.protected_symlinks: 1
fs.protected_fifos: 2

View file

@ -7,4 +7,4 @@
group: root
mode: "0644"
notify: Reload sysctl
when: chezlepro_sysctl_apply | bool
when: sysctl_hardening_apply | bool

View file

@ -1,5 +1,5 @@
# Managed by Ansible — Set-OPS
{% for key, value in chezlepro_sysctl_hardening_values | dictsort %}
{% for key, value in sysctl_hardening_values | dictsort %}
{{ key }} = {{ value }}
{% endfor %}

View file

@ -1,2 +1,2 @@
---
chezlepro_disable_systemd_ssh_auto: true
systemd_ssh_auto_disable: true

View file

@ -6,12 +6,12 @@
owner: root
group: root
mode: "0644"
when: chezlepro_disable_systemd_ssh_auto | bool
when: systemd_ssh_auto_disable | bool
notify: Update grub
- name: Supprimer le paramètre si désactivé
ansible.builtin.file:
path: /etc/default/grub.d/99-chezlepro-systemd-ssh-auto.cfg
state: absent
when: not (chezlepro_disable_systemd_ssh_auto | bool)
when: not (systemd_ssh_auto_disable | bool)
notify: Update grub

View file

@ -1,3 +1,3 @@
---
confirm_template_cleanup: false
chezlepro_admin_user: ansible
template_cleanup_confirm: false
template_cleanup_admin_user: ansible

View file

@ -2,8 +2,8 @@
- name: Refuser le nettoyage sans confirmation explicite
ansible.builtin.assert:
that:
- confirm_template_cleanup | bool
fail_msg: "Nettoyage refusé. Relancer avec -e confirm_template_cleanup=true"
- template_cleanup_confirm | bool
fail_msg: "Nettoyage refusé. Relancer avec -e template_cleanup_confirm=true"
- name: Nettoyer cloud-init
ansible.builtin.command: cloud-init clean --logs
@ -53,7 +53,7 @@
state: absent
loop:
- /root/.bash_history
- "/home/{{ chezlepro_admin_user }}/.bash_history"
- "/home/{{ template_cleanup_admin_user }}/.bash_history"
failed_when: false
- name: Message final

View file

@ -1,4 +1,4 @@
---
chezlepro_unattended_upgrades_enabled: true
chezlepro_unattended_remove_unused_dependencies: true
chezlepro_unattended_automatic_reboot: false
unattended_upgrades_enabled: true
unattended_upgrades_remove_unused_dependencies: true
unattended_upgrades_automatic_reboot: false

View file

@ -1,4 +1,4 @@
// Managed by Ansible — Set-OPS
APT::Periodic::Update-Package-Lists "{{ 1 if chezlepro_unattended_upgrades_enabled | bool else 0 }}";
APT::Periodic::Unattended-Upgrade "{{ 1 if chezlepro_unattended_upgrades_enabled | bool else 0 }}";
APT::Periodic::Update-Package-Lists "{{ 1 if unattended_upgrades_enabled | bool else 0 }}";
APT::Periodic::Unattended-Upgrade "{{ 1 if unattended_upgrades_enabled | bool else 0 }}";
APT::Periodic::AutocleanInterval "7";

View file

@ -5,7 +5,7 @@ Unattended-Upgrade::Origins-Pattern {
};
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "{{ 'true' if chezlepro_unattended_remove_unused_dependencies | bool else 'false' }}";
Unattended-Upgrade::Remove-Unused-Dependencies "{{ 'true' if chezlepro_unattended_remove_unused_dependencies | bool else 'false' }}";
Unattended-Upgrade::Automatic-Reboot "{{ 'true' if chezlepro_unattended_automatic_reboot | bool else 'false' }}";
Unattended-Upgrade::Remove-New-Unused-Dependencies "{{ 'true' if unattended_upgrades_remove_unused_dependencies | bool else 'false' }}";
Unattended-Upgrade::Remove-Unused-Dependencies "{{ 'true' if unattended_upgrades_remove_unused_dependencies | bool else 'false' }}";
Unattended-Upgrade::Automatic-Reboot "{{ 'true' if unattended_upgrades_automatic_reboot | bool else 'false' }}";
Unattended-Upgrade::SyslogEnable "true";

View file

@ -1,2 +0,0 @@
---
chezlepro_disable_systemd_ssh_auto: true

View file

@ -1,4 +0,0 @@
---
- name: Update grub
ansible.builtin.command: update-grub
changed_when: true

View file

@ -1,17 +0,0 @@
---
- name: Déployer le paramètre GRUB pour désactiver systemd.ssh_auto
ansible.builtin.template:
src: 99-chezlepro-systemd-ssh-auto.cfg.j2
dest: /etc/default/grub.d/99-chezlepro-systemd-ssh-auto.cfg
owner: root
group: root
mode: "0644"
when: chezlepro_disable_systemd_ssh_auto | bool
notify: Update grub
- name: Supprimer le paramètre si désactivé dans les variables
ansible.builtin.file:
path: /etc/default/grub.d/99-chezlepro-systemd-ssh-auto.cfg
state: absent
when: not (chezlepro_disable_systemd_ssh_auto | bool)
notify: Update grub

View file

@ -1,2 +0,0 @@
# Managed by Ansible — Set-OPS
GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} systemd.ssh_auto=no"

View file

@ -1,2 +0,0 @@
---
confirm_template_cleanup: false

View file

@ -1,54 +0,0 @@
---
- name: Refuser le nettoyage sans confirmation explicite
ansible.builtin.assert:
that:
- confirm_template_cleanup | bool
fail_msg: "Nettoyage refusé. Relancer avec -e confirm_template_cleanup=true"
- name: Nettoyer cloud-init
ansible.builtin.command: cloud-init clean --logs
changed_when: true
- name: Nettoyer le cache APT
ansible.builtin.apt:
clean: true
- name: Supprimer les paquets devenus inutiles
ansible.builtin.apt:
autoremove: true
- name: Rotation des journaux systemd
ansible.builtin.command: journalctl --rotate
changed_when: true
failed_when: false
- name: Vider les anciens journaux systemd
ansible.builtin.command: journalctl --vacuum-time=1s
changed_when: true
failed_when: false
- name: Vider /etc/machine-id
ansible.builtin.copy:
dest: /etc/machine-id
content: ""
owner: root
group: root
mode: "0444"
- name: Supprimer l'ancien machine-id D-Bus
ansible.builtin.file:
path: /var/lib/dbus/machine-id
state: absent
- name: Recréer le lien D-Bus vers /etc/machine-id
ansible.builtin.file:
src: /etc/machine-id
dest: /var/lib/dbus/machine-id
state: link
force: true
- name: Message final
ansible.builtin.debug:
msg:
- "Nettoyage final terminé."
- "Éteindre la VM puis convertir en template Proxmox."

170
scripts/config_proxmox.py Normal file
View file

@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""Assistant de configuration Proxmox pour Set-OPS."""
from __future__ import annotations
from getpass import getpass
from pathlib import Path
import os
import subprocess
import sys
import yaml
RACINE = Path(__file__).resolve().parents[1]
FICHIER_PROXMOX = RACINE / "inventories/lab/group_vars/proxmox.yml"
FICHIER_VAULT = RACINE / "inventories/lab/group_vars/proxmox.vault.yml"
VALEURS_DEFAUT = {
"proxmox_api_host": "",
"proxmox_api_user": "",
"proxmox_api_port": "",
"proxmox_validate_certs": False,
"proxmox_clone_noeud": "",
"proxmox_clone_vmid_modele": 9000,
"proxmox_clone_source_nom": "modele-debian13",
"proxmox_clone_stockage": "",
"proxmox_clone_format": "",
"proxmox_clone_complet": True,
"proxmox_clone_timeout": 600,
"proxmox_clone_disque": "scsi0",
"proxmox_clone_interface": "net0",
"proxmox_clone_pont": "vmbr0",
"proxmox_clone_parefeu_interface": False,
"proxmox_clone_demarrer": True,
}
LIBELLES = {
"proxmox_api_host": "Hote API Proxmox",
"proxmox_api_user": "Utilisateur API Proxmox",
"proxmox_api_port": "Port API Proxmox",
"proxmox_validate_certs": "Valider les certificats TLS",
"proxmox_clone_noeud": "Noeud Proxmox par defaut",
"proxmox_clone_vmid_modele": "VMID du modele Debian 13",
"proxmox_clone_source_nom": "Nom logique du modele",
"proxmox_clone_stockage": "Stockage Proxmox par defaut",
"proxmox_clone_format": "Format disque par defaut",
"proxmox_clone_complet": "Clone complet",
"proxmox_clone_timeout": "Timeout operations Proxmox",
"proxmox_clone_disque": "Disque principal",
"proxmox_clone_interface": "Interface reseau",
"proxmox_clone_pont": "Pont Proxmox",
"proxmox_clone_parefeu_interface": "Pare-feu interface Proxmox",
"proxmox_clone_demarrer": "Demarrer le clone apres creation",
}
def charger_yaml(path: Path) -> dict:
if 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 ecrire_yaml(path: Path, data: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as fichier:
fichier.write("---\n")
fichier.write("# Parametres non sensibles pour les operations Proxmox.\n")
fichier.write("# Les secrets API doivent aller dans proxmox.vault.yml.\n\n")
yaml.safe_dump(data, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
def demander(identifiant: str, courant: object) -> object:
libelle = LIBELLES[identifiant]
if isinstance(courant, bool):
defaut = "oui" if courant else "non"
reponse = input(f"{libelle} [{defaut}] : ").strip().lower()
if not reponse:
return courant
return reponse in {"o", "oui", "y", "yes", "true", "1"}
reponse = input(f"{libelle} [{courant}] : ").strip()
if not reponse:
return courant
if isinstance(courant, int):
return int(reponse)
return reponse
def configurer_proxmox() -> None:
courant = VALEURS_DEFAUT | charger_yaml(FICHIER_PROXMOX)
nouveau = {}
print("Configuration Proxmox non sensible")
print("Entrer pour conserver la valeur entre crochets.\n")
print("Les valeurs Cloud-Init deja portees par le modele ne sont pas redemandees.\n")
for identifiant in VALEURS_DEFAUT:
nouveau[identifiant] = demander(identifiant, courant.get(identifiant, VALEURS_DEFAUT[identifiant]))
ecrire_yaml(FICHIER_PROXMOX, nouveau)
print(f"\nEcrit: {FICHIER_PROXMOX.relative_to(RACINE)}")
def demander_oui_non(question: str, defaut: bool = False) -> bool:
suffixe = "O/n" if defaut else "o/N"
reponse = input(f"{question} [{suffixe}] : ").strip().lower()
if not reponse:
return defaut
return reponse in {"o", "oui", "y", "yes", "true", "1"}
def configurer_vault() -> None:
if not demander_oui_non("Configurer les secrets API Proxmox maintenant"):
return
if FICHIER_VAULT.exists():
print(f"Ouverture du Vault existant: {FICHIER_VAULT.relative_to(RACINE)}")
subprocess.run(["ansible-vault", "edit", str(FICHIER_VAULT)], check=True)
return
token_id = input("Token ID Proxmox [set-ops] : ").strip() or "set-ops"
token_secret = getpass("Token secret Proxmox : ").strip()
if not token_secret:
print("Secret vide: Vault non cree.")
return
FICHIER_VAULT.parent.mkdir(parents=True, exist_ok=True)
try:
fd = os.open(FICHIER_VAULT, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as fichier:
yaml.safe_dump(
{
"proxmox_api_token_id": token_id,
"proxmox_api_token_secret": token_secret,
},
fichier,
default_flow_style=False,
sort_keys=False,
)
subprocess.run(["ansible-vault", "encrypt", str(FICHIER_VAULT)], check=True)
print(f"Vault cree: {FICHIER_VAULT.relative_to(RACINE)}")
except Exception:
if FICHIER_VAULT.exists():
FICHIER_VAULT.unlink()
raise
def main() -> int:
try:
configurer_proxmox()
configurer_vault()
except KeyboardInterrupt:
print("\nConfiguration interrompue.", file=sys.stderr)
return 130
except Exception as exc:
print(f"erreur: {exc}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())

191
scripts/inventory_host.py Normal file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""Gere les hotes d'un inventaire Ansible statique Set-OPS."""
from __future__ import annotations
import argparse
from pathlib import Path
import sys
import yaml
def charger_inventaire(path: Path) -> dict:
if not path.exists():
return {"all": {"children": {}}}
with path.open("r", encoding="utf-8") as inventory_file:
data = yaml.safe_load(inventory_file) or {}
if not isinstance(data, dict):
raise ValueError(f"{path} ne contient pas une table YAML.")
data.setdefault("all", {})
data["all"].setdefault("children", {})
return data
def ecrire_inventaire(path: Path, data: dict) -> None:
with path.open("w", encoding="utf-8") as inventory_file:
yaml.safe_dump(
data,
inventory_file,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
def enfants(data: dict) -> dict:
return data.setdefault("all", {}).setdefault("children", {})
def assurer_groupe(data: dict, group: str) -> dict:
group_data = enfants(data).setdefault(group, {})
group_data.setdefault("hosts", {})
return group_data
def variables_hote(data: dict, host: str) -> dict:
merged: dict = {}
for group_data in enfants(data).values():
hosts = group_data.get("hosts", {})
if host in hosts and isinstance(hosts[host], dict):
merged.update(hosts[host])
return merged
def groupes_hote(data: dict, host: str) -> list[str]:
groups: list[str] = []
for group, group_data in enfants(data).items():
if host in group_data.get("hosts", {}):
groups.append(group)
return groups
def retirer_hote_de_tous_les_groupes(data: dict, host: str) -> None:
for group_data in enfants(data).values():
group_data.get("hosts", {}).pop(host, None)
def ajouter_hote(data: dict, host: str, groups: list[str], ip: str | None, ansible_user: str | None) -> None:
vars_for_host = variables_hote(data, host)
if ip:
vars_for_host["ansible_host"] = ip
if ansible_user:
vars_for_host["ansible_user"] = ansible_user
for group in groups:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
def definir_groupes_hote(data: dict, host: str, groups: list[str]) -> None:
vars_for_host = variables_hote(data, host)
if not groupes_hote(data, host):
raise ValueError(f"Hote absent de l'inventaire: {host}")
retirer_hote_de_tous_les_groupes(data, host)
for group in groups:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
def analyser_groupes(raw_groups: str) -> list[str]:
groups = [group.strip() for group in raw_groups.replace(",", " ").split() if group.strip()]
if not groups:
raise ValueError("Au moins un groupe est requis.")
return groups
def afficher_hote(data: dict, host: str) -> None:
groups = groupes_hote(data, host)
if not groups:
raise ValueError(f"Hote absent de l'inventaire: {host}")
yaml.safe_dump(
{
"hote": host,
"groupes": groups,
"variables": variables_hote(data, host),
},
sys.stdout,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
def afficher_playbooks_hote(data: dict, host: str, playbook_dir: Path) -> None:
groups = groupes_hote(data, host)
if not groups:
raise ValueError(f"Hote absent de l'inventaire: {host}")
missing_playbooks: list[str] = []
playbooks: list[Path] = []
for group in groups:
playbook = playbook_dir / f"{group}.yml"
if playbook.exists():
playbooks.append(playbook)
else:
missing_playbooks.append(group)
if missing_playbooks:
missing = ", ".join(missing_playbooks)
raise ValueError(f"Playbook de groupe manquant pour: {missing}")
for playbook in playbooks:
print(playbook)
def main() -> int:
parser = argparse.ArgumentParser(description="Gere les hotes d'un inventaire Ansible statique Set-OPS.")
parser.add_argument("--inventaire", required=True, type=Path)
subparsers = parser.add_subparsers(dest="command", required=True)
add_parser = subparsers.add_parser("ajouter", help="Ajoute ou met a jour un hote dans un ou plusieurs groupes.")
add_parser.add_argument("--hote", required=True)
add_parser.add_argument("--groupes", required=True)
add_parser.add_argument("--adresse-ip")
add_parser.add_argument("--utilisateur-ansible")
groups_parser = subparsers.add_parser("definir-groupes", help="Remplace l'appartenance aux groupes pour un hote.")
groups_parser.add_argument("--hote", required=True)
groups_parser.add_argument("--groupes", required=True)
show_parser = subparsers.add_parser("afficher", help="Affiche les groupes et variables d'un hote.")
show_parser.add_argument("--hote", required=True)
playbooks_parser = subparsers.add_parser("playbooks", help="Affiche les playbooks de groupes pour un hote.")
playbooks_parser.add_argument("--hote", required=True)
playbooks_parser.add_argument("--dossier-playbooks", required=True, type=Path)
args = parser.parse_args()
try:
data = charger_inventaire(args.inventaire)
if args.command == "ajouter":
ajouter_hote(
data,
args.hote,
analyser_groupes(args.groupes),
args.adresse_ip,
args.utilisateur_ansible,
)
ecrire_inventaire(args.inventaire, data)
elif args.command == "definir-groupes":
definir_groupes_hote(data, args.hote, analyser_groupes(args.groupes))
ecrire_inventaire(args.inventaire, data)
elif args.command == "afficher":
afficher_hote(data, args.hote)
elif args.command == "playbooks":
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
except Exception as exc:
print(f"erreur: {exc}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())