lancelot : modèle 9100 avec agent QEMU, machine-id régénéré, pool et destroy par l'API
- modèle par défaut 9100 debian13-bwpoc (dérivé de 9001 + qemu-guest-agent), fabrication en SSH root seulement si pve_template_create: true - régénération du machine-id par l'agent QEMU (sinon même IP DHCP pour tous les clones) - pool bwpoc créé par l'API, 99-destroy par l'API - correctif ansible-core 2.21 (copie de hostvars), clé SSH id_ed25519 ou id_rsa Déploiement validé sur lancelot : 50-tests 26 OK, 0 KO. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f315304849
commit
de23e19910
8 files changed
+106
-29
No files matched your search
@@ -55,7 +55,7 @@ Les mots de passe (MariaDB, Redis, admin de l'UI) sont générés au premier lan
|
|||||||
|
|
||||||
## Compte Proxmox dédié
|
## Compte Proxmox dédié
|
||||||
|
|
||||||
Le clonage et le pilotage des VM passent par l'API avec l'utilisateur `bwpoc@pve` et un jeton, limité à un pool `bwpoc`. Seule la création du modèle (import d'une image disque par chemin, réservé à `root@pam`) se fait en SSH root, une fois.
|
Le clonage et le pilotage des VM passent par l'API avec l'utilisateur `bwpoc@pve` et un jeton, limité à un pool `bwpoc`. Par défaut, le PoC clone un modèle cloud-init Debian 13 existant (`pve_template_vmid`, 9001 sur lancelot : VLAN 200, agent QEMU), donc aucun SSH root n'est nécessaire. Avec `pve_template_create: true`, le modèle 9100 est fabriqué en SSH root (import d'une image disque par chemin, réservé à `root@pam`), une fois.
|
||||||
|
|
||||||
Privilèges nécessaires, regroupés dans un rôle `BWPoC` :
|
Privilèges nécessaires, regroupés dans un rôle `BWPoC` :
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ Et les ACL, au plus juste :
|
|||||||
|
|
||||||
| Chemin | Rôle | Pourquoi |
|
| Chemin | Rôle | Pourquoi |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `/pool/bwpoc` | `BWPoC` | les VM du PoC et le modèle 9100, placé dans le pool |
|
| `/pool/bwpoc` | `BWPoC` | les VM du PoC (et le modèle 9100 s'il est fabriqué par le PoC) |
|
||||||
| `/storage/zfs` | `PVEDatastoreUser` | allouer les disques des clones |
|
| `/storage/zfs` | `PVEDatastoreUser` | allouer les disques des clones |
|
||||||
| `/sdn/zones/localnetwork/vmbr0/200` | `PVESDNUser` | brancher les VM sur le VLAN 200 (et seulement lui) |
|
| `/sdn/zones/localnetwork/vmbr0/200` | `PVESDNUser` | brancher les VM sur le VLAN 200 (et seulement lui) |
|
||||||
| `/nodes/lancelot` | `PVEAuditor` | lecture du nœud par le module Ansible |
|
| `/nodes/lancelot` | `PVEAuditor` | lecture du nœud par le module Ansible |
|
||||||
@@ -112,7 +112,7 @@ make deploy # = ansible-playbook playbooks/site.yml
|
|||||||
|
|
||||||
| Étape | Playbook | Ce qu'elle fait |
|
| Étape | Playbook | Ce qu'elle fait |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1 | `00-proxmox.yml` | en root (une fois) : télécharge l'image Debian 13 genericcloud, y ajoute l'agent QEMU, crée le modèle cloud-init (VMID 9100) et le pool `bwpoc`. Puis par l'API avec `bwpoc@pve` : clone les VM dans le pool, cloud-init en DHCP + clé SSH, agrandit les disques, démarre, lit l'IP obtenue par l'agent QEMU et l'écrit dans `inventory/host_vars/<vm>/dhcp.yml` |
|
| 1 | `00-proxmox.yml` | si `pve_template_create: true`, en root (une fois) : fabrique le modèle 9100 depuis l'image Debian 13 genericcloud. Puis par l'API : crée le pool `bwpoc`, clone le modèle (9001 par défaut), cloud-init en DHCP + clé SSH, agrandit les disques, démarre, régénère le machine-id par l'agent QEMU (sinon tous les clones obtiennent la même IP DHCP), lit l'IP obtenue par l'agent QEMU et l'écrit dans `inventory/host_vars/<vm>/dhcp.yml` |
|
||||||
| 2 | `10-base.yml` | paquets de base, agent QEMU, résolution locale des noms du PoC vers le WAF, Docker CE depuis le dépôt officiel |
|
| 2 | `10-base.yml` | paquets de base, agent QEMU, résolution locale des noms du PoC vers le WAF, Docker CE depuis le dépôt officiel |
|
||||||
| 3 | `20-apps.yml` | stack des applis, filtrage `DOCKER-USER` (mode split) |
|
| 3 | `20-apps.yml` | stack des applis, filtrage `DOCKER-USER` (mode split) |
|
||||||
| 4 | `30-bunkerweb.yml` | stack BunkerWeb, attente qu'`api` réponde au travers du WAF, écriture de `credentials/ACCES.txt` |
|
| 4 | `30-bunkerweb.yml` | stack BunkerWeb, attente qu'`api` réponde au travers du WAF, écriture de `credentials/ACCES.txt` |
|
||||||
@@ -233,8 +233,8 @@ La SQLi de Juice Shop renvoie alors la liste complète des produits (200) au lie
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make waf # réapplique la conf BunkerWeb après modification de bunkerweb.yml
|
make waf # réapplique la conf BunkerWeb après modification de bunkerweb.yml
|
||||||
make destroy # supprime les VM en root sur le nœud (confirmation demandée), modèle conservé
|
make destroy # supprime les VM par l'API (confirmation demandée), modèle conservé
|
||||||
ansible-playbook playbooks/99-destroy.yml -e destroy_template=true # supprime aussi le modèle
|
ansible-playbook playbooks/99-destroy.yml -e destroy_template=true # supprime aussi le modèle, s'il a été fabriqué par le PoC
|
||||||
```
|
```
|
||||||
|
|
||||||
Sur `bw-waf`, la stack vit dans `/opt/bwpoc/bunkerweb` (`docker compose ps`, `logs`, `pull`...). Sur `bw-apps`, dans `/opt/bwpoc/apps`.
|
Sur `bw-waf`, la stack vit dans `/opt/bwpoc/bunkerweb` (`docker compose ps`, `logs`, `pull`...). Sur `bw-apps`, dans `/opt/bwpoc/apps`.
|
||||||
|
|||||||
@@ -25,14 +25,19 @@ pve_api_token_id: ansible
|
|||||||
pve_api_token_secret: "{{ lookup('ansible.builtin.env', 'PVE_TOKEN_SECRET') }}"
|
pve_api_token_secret: "{{ lookup('ansible.builtin.env', 'PVE_TOKEN_SECRET') }}"
|
||||||
pve_api_password: "{{ lookup('ansible.builtin.env', 'PROXMOX_PASSWORD') }}"
|
pve_api_password: "{{ lookup('ansible.builtin.env', 'PROXMOX_PASSWORD') }}"
|
||||||
pve_validate_certs: false # certificat auto-signé du nœud
|
pve_validate_certs: false # certificat auto-signé du nœud
|
||||||
|
# Modèle cloud-init Debian 13 AVEC qemu-guest-agent installé et machine-id vierge.
|
||||||
|
# Sur lancelot : 9100 "debian13-bwpoc", dérivé de 9001 (agent ajouté, cloud-init clean).
|
||||||
|
# pve_template_create: true pour le fabriquer depuis l'image officielle (SSH root sur le nœud).
|
||||||
|
pve_template_create: false
|
||||||
pve_template_vmid: 9100
|
pve_template_vmid: 9100
|
||||||
pve_template_name: debian13-cloud
|
pve_template_name: debian13-bwpoc
|
||||||
pve_image_url: https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2
|
pve_image_url: https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2
|
||||||
pve_image_path: /var/lib/vz/template/debian-13-genericcloud-amd64.qcow2
|
pve_image_path: /var/lib/vz/template/debian-13-genericcloud-amd64.qcow2
|
||||||
|
|
||||||
# --- Accès aux VM -----------------------------------------------------
|
# --- Accès aux VM -----------------------------------------------------
|
||||||
poc_vm_user: debian
|
poc_vm_user: debian
|
||||||
poc_ssh_pubkey_file: "~/.ssh/id_ed25519.pub" # clé injectée par cloud-init
|
# Clé injectée par cloud-init : la première trouvée.
|
||||||
|
poc_ssh_pubkey_file: "{{ lookup('ansible.builtin.first_found', ['~/.ssh/id_ed25519.pub', '~/.ssh/id_rsa.pub'] | map('expanduser') | list) }}"
|
||||||
|
|
||||||
# --- Versions (épinglées) --------------------------------------------
|
# --- Versions (épinglées) --------------------------------------------
|
||||||
bunkerweb_version: "1.6.17"
|
bunkerweb_version: "1.6.17"
|
||||||
|
|||||||
+1
-1
@@ -36,4 +36,4 @@ all:
|
|||||||
vm_id: 9103
|
vm_id: 9103
|
||||||
vm_cores: 1
|
vm_cores: 1
|
||||||
vm_memory: 1024
|
vm_memory: 1024
|
||||||
vm_disk_gb: 10
|
vm_disk_gb: 20 # pas plus petit que le disque du modèle (20 Go)
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
hosts: proxmox
|
hosts: proxmox
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
roles:
|
roles:
|
||||||
- proxmox_template
|
- role: proxmox_template
|
||||||
|
when: pve_template_create | bool
|
||||||
|
|
||||||
# 2) Clonage des VM par l'API, avec le compte dédié bwpoc@pve (jeton API).
|
# 2) Clonage des VM par l'API, avec le compte dédié bwpoc@pve (jeton API).
|
||||||
# Authentification : jeton (PVE_TOKEN_SECRET) ou mot de passe (PROXMOX_CLAUDE / PROXMOX_PASSWORD).
|
# Authentification : jeton (PVE_TOKEN_SECRET) ou mot de passe (PROXMOX_CLAUDE / PROXMOX_PASSWORD).
|
||||||
|
|||||||
+27
-19
@@ -2,7 +2,19 @@
|
|||||||
# Supprime les VM du PoC (le modèle est conservé, sauf -e destroy_template=true).
|
# Supprime les VM du PoC (le modèle est conservé, sauf -e destroy_template=true).
|
||||||
- name: Détruire les VM du PoC
|
- name: Détruire les VM du PoC
|
||||||
hosts: proxmox
|
hosts: proxmox
|
||||||
|
connection: local
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
ansible_become: false
|
||||||
|
ansible_python_interpreter: "{{ ansible_playbook_python }}"
|
||||||
|
module_defaults:
|
||||||
|
group/community.proxmox.proxmox:
|
||||||
|
api_host: "{{ ansible_host }}"
|
||||||
|
api_user: "{{ pve_api_user }}"
|
||||||
|
api_token_id: "{{ pve_api_token_id if pve_api_token_secret | length > 0 else omit }}"
|
||||||
|
api_token_secret: "{{ pve_api_token_secret if pve_api_token_secret | length > 0 else omit }}"
|
||||||
|
api_password: "{{ pve_api_password if pve_api_token_secret | length == 0 else omit }}"
|
||||||
|
validate_certs: "{{ pve_validate_certs }}"
|
||||||
vars_prompt:
|
vars_prompt:
|
||||||
- name: confirm
|
- name: confirm
|
||||||
prompt: "Taper 'detruire' pour supprimer les VM {{ groups['poc_vms'] | unique | join(', ') }}"
|
prompt: "Taper 'detruire' pour supprimer les VM {{ groups['poc_vms'] | unique | join(', ') }}"
|
||||||
@@ -12,30 +24,26 @@
|
|||||||
ansible.builtin.meta: end_play
|
ansible.builtin.meta: end_play
|
||||||
when: confirm != 'detruire'
|
when: confirm != 'detruire'
|
||||||
|
|
||||||
- name: Arrêter et supprimer les VM
|
- name: Arrêter et supprimer les VM (API)
|
||||||
ansible.builtin.shell: |
|
community.proxmox.proxmox_kvm:
|
||||||
if qm status {{ hostvars[item].vm_id }} >/dev/null 2>&1; then
|
node: "{{ pve_node }}"
|
||||||
qm stop {{ hostvars[item].vm_id }} --skiplock 1 || true
|
vmid: "{{ hostvars[item].vm_id }}"
|
||||||
qm destroy {{ hostvars[item].vm_id }} --purge 1 --destroy-unreferenced-disks 1
|
state: absent
|
||||||
echo destroyed
|
force: true
|
||||||
fi
|
timeout: 300
|
||||||
register: destroy
|
|
||||||
changed_when: "'destroyed' in destroy.stdout"
|
|
||||||
loop: "{{ groups['poc_vms'] | unique }}"
|
loop: "{{ groups['poc_vms'] | unique }}"
|
||||||
|
|
||||||
- name: Supprimer le modèle
|
- name: Supprimer le modèle (seulement s'il a été créé par le PoC)
|
||||||
ansible.builtin.shell: |
|
community.proxmox.proxmox_kvm:
|
||||||
if qm status {{ pve_template_vmid }} >/dev/null 2>&1; then
|
node: "{{ pve_node }}"
|
||||||
qm destroy {{ pve_template_vmid }} --purge 1 && echo destroyed
|
vmid: "{{ pve_template_vmid }}"
|
||||||
fi
|
state: absent
|
||||||
register: tpl_destroy
|
when:
|
||||||
changed_when: "'destroyed' in tpl_destroy.stdout"
|
- destroy_template | default(false) | bool
|
||||||
when: destroy_template | default(false) | bool
|
- pve_template_create | bool
|
||||||
|
|
||||||
- name: Oublier les IP DHCP mémorisées
|
- name: Oublier les IP DHCP mémorisées
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ inventory_dir }}/host_vars/{{ item }}/dhcp.yml"
|
path: "{{ inventory_dir }}/host_vars/{{ item }}/dhcp.yml"
|
||||||
state: absent
|
state: absent
|
||||||
loop: "{{ groups['poc_vms'] | unique }}"
|
loop: "{{ groups['poc_vms'] | unique }}"
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
# Exécution dans la VM par l'agent QEMU (API /agent/exec), sans SSH.
|
||||||
|
- name: "{{ vm_name }} : authentification à l'API"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://{{ ansible_host }}:8006/api2/json/access/ticket"
|
||||||
|
method: POST
|
||||||
|
body_format: form-urlencoded
|
||||||
|
body:
|
||||||
|
username: "{{ pve_api_user }}"
|
||||||
|
password: "{{ pve_api_password }}"
|
||||||
|
validate_certs: "{{ pve_validate_certs }}"
|
||||||
|
register: pve_ticket
|
||||||
|
no_log: true
|
||||||
|
when: pve_api_token_secret | length == 0
|
||||||
|
|
||||||
|
- name: "{{ vm_name }} : en-têtes d'API"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pve_headers: >-
|
||||||
|
{{ {'Authorization': 'PVEAPIToken=' ~ pve_api_user ~ '!' ~ pve_api_token_id ~ '=' ~ pve_api_token_secret}
|
||||||
|
if pve_api_token_secret | length > 0 else
|
||||||
|
{'Cookie': 'PVEAuthCookie=' ~ pve_ticket.json.data.ticket,
|
||||||
|
'CSRFPreventionToken': pve_ticket.json.data.CSRFPreventionToken} }}
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: "{{ vm_name }} : attente de l'agent QEMU"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://{{ ansible_host }}:8006/api2/json/nodes/{{ pve_node }}/qemu/{{ vm.vm_id }}/agent/ping"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ pve_headers }}"
|
||||||
|
validate_certs: "{{ pve_validate_certs }}"
|
||||||
|
register: agent_ping
|
||||||
|
until: agent_ping.status == 200
|
||||||
|
retries: 60 # premier démarrage cloud-init : jusqu'à 5 min
|
||||||
|
delay: 5
|
||||||
|
|
||||||
|
- name: "{{ vm_name }} : régénérer le machine-id puis redémarrer"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://{{ ansible_host }}:8006/api2/json/nodes/{{ pve_node }}/qemu/{{ vm.vm_id }}/agent/exec"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ pve_headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
command: ["sh", "-c", "test -f /etc/bwpoc-machine-id || { rm -f /etc/machine-id /var/lib/dbus/machine-id && systemd-machine-id-setup && touch /etc/bwpoc-machine-id && systemctl reboot; }"]
|
||||||
|
validate_certs: "{{ pve_validate_certs }}"
|
||||||
|
|
||||||
|
- name: "{{ vm_name }} : laisser le redémarrage commencer"
|
||||||
|
ansible.builtin.pause:
|
||||||
|
seconds: 20
|
||||||
@@ -7,6 +7,11 @@
|
|||||||
fail_msg: "Exportez PVE_TOKEN_SECRET (jeton {{ pve_api_user }}!{{ pve_api_token_id }}) ou PROXMOX_CLAUDE et PROXMOX_PASSWORD"
|
fail_msg: "Exportez PVE_TOKEN_SECRET (jeton {{ pve_api_user }}!{{ pve_api_token_id }}) ou PROXMOX_CLAUDE et PROXMOX_PASSWORD"
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
|
- name: Créer le pool {{ pve_pool }}
|
||||||
|
community.proxmox.proxmox_pool:
|
||||||
|
poolid: "{{ pve_pool }}"
|
||||||
|
comment: PoC BunkerWeb
|
||||||
|
|
||||||
- name: Créer le répertoire des host_vars par VM
|
- name: Créer le répertoire des host_vars par VM
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ inventory_dir }}/host_vars/{{ item }}"
|
path: "{{ inventory_dir }}/host_vars/{{ item }}"
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
---
|
---
|
||||||
- name: "{{ vm_name }} : lecture des paramètres"
|
- name: "{{ vm_name }} : lecture des paramètres"
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
vm: "{{ hostvars[vm_name] }}"
|
# Uniquement les champs utiles : copier tout hostvars[vm] échoue avec ansible-core 2.21
|
||||||
|
vm:
|
||||||
|
vm_id: "{{ hostvars[vm_name].vm_id }}"
|
||||||
|
vm_cores: "{{ hostvars[vm_name].vm_cores }}"
|
||||||
|
vm_memory: "{{ hostvars[vm_name].vm_memory }}"
|
||||||
|
vm_disk_gb: "{{ hostvars[vm_name].vm_disk_gb }}"
|
||||||
|
|
||||||
- name: "{{ vm_name }} : la VM existe-t-elle ?"
|
- name: "{{ vm_name }} : la VM existe-t-elle ?"
|
||||||
community.proxmox.proxmox_vm_info:
|
community.proxmox.proxmox_vm_info:
|
||||||
@@ -54,6 +59,11 @@
|
|||||||
state: started
|
state: started
|
||||||
timeout: 120
|
timeout: 120
|
||||||
|
|
||||||
|
# Un clone hérite du machine-id du modèle : systemd-networkd en dérive son identifiant
|
||||||
|
# DHCP, et toutes les VM recevraient la même IP. On le régénère une fois (marqueur /etc/bwpoc-machine-id).
|
||||||
|
- name: "{{ vm_name }} : nouveau machine-id (identifiant DHCP propre)"
|
||||||
|
ansible.builtin.include_tasks: machine_id.yml
|
||||||
|
|
||||||
- name: "{{ vm_name }} : adresse DHCP remontée par l'agent QEMU"
|
- name: "{{ vm_name }} : adresse DHCP remontée par l'agent QEMU"
|
||||||
community.proxmox.proxmox_vm_info:
|
community.proxmox.proxmox_vm_info:
|
||||||
node: "{{ pve_node }}"
|
node: "{{ pve_node }}"
|
||||||
|
|||||||
Reference in new issue
Block a user