OPNsense as Code avec Ansible : déployer et versionner toute l'infrastructure en une commande
Déployer et versionner toute l'infrastructure OPNsense avec Ansible : playbook complet VLAN/firewall/WireGuard/CrowdSec, GitOps et disaster recovery en < 15 minutes.

1. Pourquoi Infrastructure as Code pour un homelab/PME ?
Après 11 billets à construire un stack de sécurité réseau complet, une question s'impose : comment reproduire exactement ce travail sur un nouveau firewall en cas de panne matérielle, de migration ou d'expansion ? Réponse : l'Infrastructure as Code (IaC).
Sans IaC, chaque changement de configuration OPNsense est un geste manuel — cliquer dans l'interface, copier-coller des règles, espérer ne rien oublier. Avec IaC, toute la configuration est du code versionné dans Git, rejouable en une commande. Pour un homelab ou une PME, les bénéfices sont concrets :
- ✓ Reproductibilité : nouveau firewall opérationnel en < 10 minutes
- ✓ Versionning :
git logmontre chaque changement, qui, quand, pourquoi - ✓ Idempotence : relancer le playbook = même état, jamais de régression
- ✓ Documentation vivante : le code IS la documentation de votre réseau
- ✓ Disaster recovery :
git clone+ansible-playbook= infrastructure restaurée - ✓ Collaboration : partager la config sans donner accès au firewall
Ansible est l'outil idéal pour OPNsense : agentless (aucun daemon à installer), utilise SSH et l'API REST OPNsense, et dispose d'une collection spécialisée — ansibleguy.opnsense.
2. Prérequis : Ansible, SSH et API OPNsense
Avant d'écrire la première ligne de YAML, il faut configurer trois choses : Ansible sur la machine de contrôle, l'accès SSH à OPNsense, et les credentials API.
2.1 Installer Ansible
# Ubuntu / Debian
sudo apt update && sudo apt install -y ansible python3-pip
pip3 install requests
# macOS
brew install ansible
# Vérifier
ansible --version
# ansible [core 2.16+]
2.2 Activer SSH sur OPNsense
# OPNsense : System > Settings > Administration
# ✓ Enable Secure Shell
# ✓ SSH port : 22 (ou port personnalisé)
# ✓ Permit password login : désactiver après ajout clé
# Ajouter votre clé publique :
# System > Access > Users > admin > Authorized keys
# Tester depuis la machine de contrôle :
ssh admin@192.168.1.1 'echo OK'
2.3 Créer un utilisateur API OPNsense
# OPNsense : System > Access > Users
# Créer user 'ansible-api' avec droits admin
# System > Access > Users > ansible-api > API keys
# Générer une paire clé/secret
# Tester l'API :
curl -k -u "${OPNSENSE_API_KEY}:${OPNSENSE_API_SECRET}" \
"https://192.168.1.1/api/core/firmware/status"
# {"status":"none"} → OK

3. Collection ansibleguy.opnsense : installation et structure
La collection ansibleguy.opnsense est la référence pour automatiser OPNsense via Ansible. Elle couvre interfaces, VLANs, règles firewall, VPN WireGuard, DNS, CrowdSec, et bien plus.
# Installer la collection
ansible-galaxy collection install ansibleguy.opnsense
# Structure recommandée du projet
opnsense-ansible/
├── inventory/
│ ├── hosts.yml # Hôtes OPNsense
│ └── group_vars/
│ └── opnsense.yml # Variables globales + credentials API
├── playbooks/
│ ├── site.yml # Playbook principal (tout le stack)
│ ├── vlans.yml # VLANs uniquement
│ ├── firewall.yml # Règles firewall
│ ├── wireguard.yml # VPN WireGuard
│ └── crowdsec.yml # CrowdSec bouncer
├── roles/
│ └── opnsense_base/ # Rôle de base (NTP, DNS, SMTP)
├── ansible.cfg
└── README.md
# inventory/hosts.yml
all:
children:
opnsense:
hosts:
fw-primary:
ansible_host: 192.168.1.1
ansible_user: admin
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
fw-secondary: # CARP HA (Billet 10)
ansible_host: 192.168.1.2
ansible_user: admin
# inventory/group_vars/opnsense.yml
opnsense_api_host: "https://192.168.1.1"
opnsense_api_key: "{{ lookup('env', 'OPNSENSE_API_KEY') }}"
opnsense_api_secret: "{{ lookup('env', 'OPNSENSE_API_SECRET') }}"
opnsense_ssl_verify: false
4. Playbook complet : VLANs, firewall, WireGuard, CrowdSec
Voici le playbook principal qui configure l'intégralité du stack en une seule commande. Chaque section correspond à un rôle du stack BOTUM.
4.1 VLANs et interfaces
# playbooks/site.yml
---
- name: "BOTUM Stack — Configuration complète OPNsense"
hosts: opnsense
gather_facts: false
collections:
- ansibleguy.opnsense
tasks:
- name: "VLAN 10 — Management"
ansibleguy.opnsense.vlan:
vlan_id: 10
interface: em0
description: "VLAN Management"
state: present
- name: "VLAN 20 — Servers"
ansibleguy.opnsense.vlan:
vlan_id: 20
interface: em0
description: "VLAN Servers"
state: present
- name: "VLAN 30 — IoT"
ansibleguy.opnsense.vlan:
vlan_id: 30
interface: em0
description: "VLAN IoT isolé"
state: present
- name: "VLAN 40 — DMZ"
ansibleguy.opnsense.vlan:
vlan_id: 40
interface: em0
description: "VLAN DMZ"
state: present
4.2 Règles firewall Zero-Trust
# ── RÈGLES FIREWALL ────────────────────────────────────────────────
- name: "Règle — Autoriser Management vers WAN"
ansibleguy.opnsense.rule:
interface: "vlan10"
source: "vlan10 net"
destination: "any"
protocol: "any"
action: "pass"
description: "MGMT vers WAN autorisé"
state: present
- name: "Règle — Bloquer IoT vers LAN"
ansibleguy.opnsense.rule:
interface: "vlan30"
source: "vlan30 net"
destination: "vlan10 net"
protocol: "any"
action: "block"
description: "IoT ne peut pas accéder LAN/MGMT"
log: true
state: present
- name: "Règle — DNS IoT vers AdGuard uniquement"
ansibleguy.opnsense.rule:
interface: "vlan30"
source: "vlan30 net"
destination: "192.168.20.5"
destination_port: "53"
protocol: "tcp/udp"
action: "pass"
state: present
4.3 WireGuard VPN et CrowdSec
# ── WIREGUARD ──────────────────────────────────────────────────────
- name: "WireGuard — Instance principale"
ansibleguy.opnsense.wireguard_server:
name: "botum-vpn"
port: 51820
private_key: "{{ lookup('env', 'WG_PRIVATE_KEY') }}"
dns: "192.168.20.5"
tunnel_address: "10.10.0.1/24"
state: present
- name: "WireGuard — Peer mobile"
ansibleguy.opnsense.wireguard_peer:
name: "phone-mobile"
public_key: "{{ lookup('env', 'WG_PEER_PUBKEY') }}"
allowed_ips: "10.10.0.2/32"
instance: "botum-vpn"
state: present
# ── CROWDSEC ───────────────────────────────────────────────────────
- name: "CrowdSec — activer le paquet"
ansibleguy.opnsense.package:
name: "os-crowdsec"
state: present
- name: "CrowdSec — configurer bouncer"
ansibleguy.opnsense.crowdsec:
enabled: true
bouncer_api_key: "{{ lookup('env', 'CROWDSEC_BOUNCER_KEY') }}"
crowdsec_api_url: "http://192.168.20.10:8080"
state: present
# ── LANCER ──────────────────────────────────────────────────────────────
# ansible-playbook -i inventory/hosts.yml playbooks/site.yml
# ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check

5. Versionner la config dans Git (GitOps pour OPNsense)
La puissance de l'IaC vient du versioning Git : chaque modification est tracée, réversible, et documentée automatiquement.
# Initialiser le dépôt Git
cd opnsense-ansible/
git init
git add .
git commit -m "feat: initial stack BOTUM complet (12 billets)"
# .gitignore — ne jamais committer les secrets
*.vault
.env
secrets/
inventory/group_vars/vault.yml
# Utiliser ansible-vault pour chiffrer les secrets
ansible-vault encrypt inventory/group_vars/vault.yml
# ansible.cfg
[defaults]
vault_password_file = ~/.vault_pass
inventory = inventory/hosts.yml
# Workflow GitOps quotidien :
# 1. Modifier un playbook
# 2. git add + git commit -m "fix: règle IoT plus restrictive"
# 3. git push origin main
# 4. CI/CD applique automatiquement
Chaque commit documente l'histoire de votre infrastructure réseau. git log --oneline vous dit exactement quand vous avez ajouté WireGuard, durci les règles IoT, ou activé CrowdSec.
6. Stratégie idempotente : relancer sans risque
L'idempotence est la propriété fondamentale d'Ansible : relancer le même playbook 10 fois produit toujours le même état final, sans créer de doublons ni casser ce qui fonctionne.
# Tester l'idempotence avant de déployer
ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check
# → "changed=0 unreachable=0 failed=0" = déjà à l'état désiré
# Mode diff : voir exactement ce qui changerait
ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check --diff
# Exemple de sortie idempotente :
# TASK [VLAN 10 — Management]
# ok: [fw-primary] ← déjà correct, rien à faire
# TASK [Règle — Bloquer IoT vers LAN]
# changed: [fw-primary] ← nouvelle règle à appliquer
# Tags pour cibler une partie spécifique
ansible-playbook site.yml --tags "vlans"
ansible-playbook site.yml --tags "firewall"
ansible-playbook site.yml --skip-tags "crowdsec"
La règle d'or : toujours lancer en --check avant un vrai déploiement. Vous voyez exactement ce qui va changer, sans risque.

7. Intégration CI/CD : GitHub Actions et Gitea
Automatiser le déploiement à chaque commit. Dès que vous poussez une modification sur la branche main, le pipeline valide et déploie automatiquement.
7.1 GitHub Actions
# .github/workflows/deploy-opnsense.yml
name: Deploy OPNsense Stack
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ansible-lint
run: pip install ansible-lint
- name: Lint playbooks
run: ansible-lint playbooks/site.yml
deploy:
runs-on: ubuntu-latest
needs: lint
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Install Ansible + collection
run: |
pip install ansible
ansible-galaxy collection install ansibleguy.opnsense
- name: Deploy to OPNsense
env:
OPNSENSE_API_KEY: ${{ secrets.OPNSENSE_API_KEY }}
OPNSENSE_API_SECRET: ${{ secrets.OPNSENSE_API_SECRET }}
ANSIBLE_VAULT_PASSWORD: ${{ secrets.VAULT_PASSWORD }}
run: |
echo "$ANSIBLE_VAULT_PASSWORD" > /tmp/.vault_pass
ansible-playbook -i inventory/hosts.yml playbooks/site.yml \
--vault-password-file /tmp/.vault_pass
- name: Notifier Telegram
if: always()
run: |
MSG="🔧 OPNsense deploy: ${{ job.status }}"
curl -s "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}&text=${MSG}"
7.2 Gitea self-hosted
# .gitea/workflows/deploy-opnsense.yml
name: Deploy OPNsense
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Ansible
run: |
pip install ansible
ansible-galaxy collection install ansibleguy.opnsense
- name: Dry-run
env:
OPNSENSE_API_KEY: ${{ secrets.OPNSENSE_API_KEY }}
OPNSENSE_API_SECRET: ${{ secrets.OPNSENSE_API_SECRET }}
run: ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check
- name: Deploy
env:
OPNSENSE_API_KEY: ${{ secrets.OPNSENSE_API_KEY }}
OPNSENSE_API_SECRET: ${{ secrets.OPNSENSE_API_SECRET }}
run: ansible-playbook -i inventory/hosts.yml playbooks/site.yml
# Runner self-hosted : docker run gitea/act_runner register
8. Disaster Recovery : restaurer en une commande
Le scénario le plus redouté : le firewall grille. Avec le stack IaC, le disaster recovery devient un exercice de routine.
# SCÉNARIO : OPNsense principal hors service
# Temps de restauration cible : < 15 minutes
# Étape 1 : Provisionner un nouveau OPNsense (Proxmox, ~5 min)
# → Installer l'ISO, configuration réseau de base via console
# Étape 2 : Restaurer la config Ansible
git clone https://gitea.botum.ca/botum/opnsense-ansible.git
cd opnsense-ansible
# Étape 3 : Déployer le stack complet (~8 min)
OPNSENSE_API_KEY=xxx OPNSENSE_API_SECRET=yyy \
ansible-playbook -i inventory/hosts.yml playbooks/site.yml
# Résultat :
# ✓ VLANs configurés (Billet 2)
# ✓ Règles firewall (Billet 2 + 3)
# ✓ WireGuard restauré (Billet 4)
# ✓ CrowdSec activé (Billet 3)
# ✓ Suricata IDS/IPS (Billet 7)
# Étape 4 : Vérifier l'idempotence
ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check
# changed=0 unreachable=0 failed=0 → Stack 100% conforme
En comparaison, une restauration manuelle prendrait 2 à 4 heures, avec le risque d'oublier des règles critiques. L'IaC transforme le disaster recovery en procédure documentée et testable.

9. Conclusion : récapitulatif du stack complet 12 billets
Avec ce Billet 12, le Stack OPNsense BOTUM est complet. En 12 billets, nous avons construit une infrastructure de sécurité réseau de niveau enterprise, reproductible, versionnée et automatisée :
- • Billet 1 — OPNsense sur Proxmox : fondation hyperviseur
- • Billet 2 — VLANs Zero-Trust : segmentation réseau
- • Billet 3 — CrowdSec IPS collaboratif : blocage communautaire
- • Billet 4 — WireGuard VPN : accès sécurisé à distance
- • Billet 5 — fail2ban renforcé : protection brute-force
- • Billet 6 — NAC : contrôle d'accès au réseau
- • Billet 7 — Suricata IDS/IPS : détection d'intrusion profonde
- • Billet 8 — AdGuard Home + DoH/DoT : DNS filtré et chiffré
- • Billet 9 — Grafana + InfluxDB : monitoring réseau
- • Billet 10 — CARP haute disponibilité : résilience infrastructure
- • Billet 11 — Wazuh SIEM : corrélation et détection
- • Billet 12 — Ansible as Code : automatisation et GitOps
Le stack est maintenant entièrement reproductible : un nouveau collaborateur peut déployer l'intégralité de cette infrastructure en moins de 30 minutes, depuis zéro, grâce à Ansible.
Prochains billets (13-15) : Backup automatisé de la config OPNsense, certificats Let's Encrypt avec ACME, et analyse de trafic avec Netflow + ntopng — pour compléter le stack.
Téléchargez ce guide en PDF pour le consulter hors ligne.
⬇ Télécharger le guide (PDF)🚀 Aller plus loin avec BOTUM
Ce guide couvre les bases. En production, chaque environnement a ses spécificités. Les équipes BOTUM accompagnent les organisations dans le déploiement, la configuration avancée et la sécurisation de leur infrastructure. Si vous avez un projet, parlons-en.
Discuter de votre projet →