premier commit

This commit is contained in:
Daniel Allaire 2026-01-12 18:05:54 -05:00
commit bd5947b5d7
68 changed files with 3782 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.retry
*.log
inventories/**/group_vars/vault.yml

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# Chezlepro Ansible (nouveau dépôt)
Dépôt Ansible normalisé (inventaire + playbooks + rôles) pour l'écosystème Chezlepro.
## Démarrage rapide
1) Ajuster l'inventaire :
- `inventories/prod/hosts.yml`
- `inventories/prod/group_vars/all.yml`
- (optionnel) `inventories/prod/group_vars/vault.yml` (Ansible Vault)
2) Exécuter :
```bash
ansible-playbook -i inventories/prod/hosts.yml playbooks/site.yml
```
## Playbooks
- `playbooks/site.yml` : orchestration globale (baseline + sécurité + services)
- `playbooks/proxmox_deb12_cloudinit.yml` : provisioning Proxmox (template Debian 12 + clones)
## Rôles
- Baseline : `baseline_packages`, `baseline_updates`
- Sécurité : `security_hardening`, `firewall_nftables`
- Services : `monitoring_icinga`, `dns_powerdns`, `reverse_proxy_traefik`
- Proxmox : `proxmox_deb12_cloudinit`

10
ansible.cfg Normal file
View file

@ -0,0 +1,10 @@
[defaults]
inventory = inventories/prod/hosts.yml
stdout_callback = yaml
interpreter_python = auto_silent
host_key_checking = False
retry_files_enabled = False
forks = 10
[privilege_escalation]
become = True

View file

@ -0,0 +1,36 @@
# OPNsense — Journal des modifications
## Règles
- 1 changement = 1 entrée
- Toujours joindre un export de config **post-changement**
- Écrire “rollback” possible en une phrase
---
## Format dentrée
- Date/heure :
- Auteur :
- Contexte :
- Changement :
- Impact attendu :
- Validation effectuée :
- Export config :
- Rollback :
---
## Entrées
### YYYY-MM-DD HH:MM
- Auteur :
- Contexte :
- Changement :
- Impact attendu :
- Validation effectuée :
- [ ] Accès admin OK
- [ ] DNS OK
- [ ] NTP OK
- [ ] LAN/WAN OK
- [ ] Monitoring OK
- Export config : `opnsense-<site>-<role>-YYYYMMDD-HHMM.xml`
- Rollback : importer lexport précédent `...`

178
docs/opnsense-runbook.md Normal file
View file

@ -0,0 +1,178 @@
# OPNsense — Runbook (générique)
## Objectif
Ce document décrit une méthode simple et reproductible pour :
- opérer un pare-feu OPNsense sans Infrastructure-as-Code,
- garder une source de vérité,
- restaurer rapidement en cas de panne,
- limiter les erreurs humaines (drift, changements non documentés).
## Portée
- OPNsense (pare-feu, routage, NAT, DHCP/DNS si applicable)
- Sauvegarde/restauration de configuration
- Cadre de gestion des changements
- Vérifications post-changement
- Intégration opérationnelle (monitoring, inventaire)
## Principes
1. **UI = configuration** (source de vérité opérationnelle).
2. **Chaque changement = export de config + note de changelog**.
3. **Restauration testée** au moins 1 fois par trimestre (sur matériel de spare ou VM si possible).
4. **Secrets** : jamais en clair dans Git; chiffrer ou stocker dans Nextcloud avec contrôle daccès.
---
## Pré-requis (à compléter)
- Modèle matériel : __________________________
- Version OPNsense : _________________________
- Mode HA (si applicable) : ___________________
- Accès admin : ______________________________
- Emplacement des exports : ___________________
- Emplacement des backups chiffrés : __________
---
## Inventaire minimal (à maintenir)
### Interfaces
| Nom | Rôle | VLAN | IPv4/IPv6 | Notes |
|---|---|---:|---|---|
| WAN | Internet | - | - | - |
| LAN | Interne | - | - | - |
| OPTx | Réseau/VLAN | - | - | - |
### Réseaux / VLAN
- LAN: ______________________
- VLANs: _____________________
- Réseaux de services (DNS/NTP/monitoring): _____________________
### Services activés
- DHCP : oui/non (sur quels réseaux)
- DNS (Unbound) : oui/non
- NTP : oui/non
- VPN : oui/non (type : WireGuard/OpenVPN/IPsec)
- Captive portal : oui/non
- IDS/IPS : oui/non (Suricata, etc.)
---
## Sauvegarde (backup) de configuration
### Quand sauvegarder
- Après **tout changement** (immédiatement après validation).
- Avant toute mise à jour majeure.
- Sauvegarde planifiée : hebdo (recommandé).
### Comment sauvegarder (procédure manuelle)
1. Se connecter à lUI OPNsense.
2. Aller dans **System → Configuration → Backups** (ou équivalent selon version).
3. Exporter la configuration (format XML).
4. Nommer le fichier :
- `opnsense-<site>-<role>-YYYYMMDD-HHMM.xml`
5. Stocker :
- copie “opérations” (Nextcloud privé / stockage interne),
- copie “reprise” (stockage secondaire hors site, chiffrée si possible).
### Convention de stockage recommandée
- `backups/opnsense/<site>/<YYYY>/<MM>/opnsense-...xml`
- Un index `backups/opnsense/README.md` expliquant où est la dernière version.
### Protection des backups
- Restreindre laccès (admin seulement).
- Si Git est utilisé : **chiffrer** (ex. fichier chiffré + clé hors Git).
---
## Restauration (disaster recovery)
### Scénarios
- Remplacement SSD / panne matérielle
- Mauvaise configuration (lockout)
- Mise à jour ratée
- Migration vers nouveau matériel
### Procédure de restauration (standard)
1. Installer OPNsense (version identique si possible).
2. Configurer accès minimal (WAN/LAN + admin).
3. Importer le fichier XML (System → Configuration → Restore).
4. Redémarrer si requis.
5. Vérifier :
- interfaces / VLAN / gateways
- DNS/NTP
- règles firewall (ordre, alias)
- NAT/port forward
- VPN (si utilisé)
6. Valider la connectivité “de bout en bout”.
### Procédure en cas de lockout
- Avoir une méthode “console”/local :
- accès clavier/écran ou IPMI (si disponible),
- rollback via backup,
- vérifier règle admin “allow” sur LAN de management.
---
## Gestion des changements
### Règle simple
- Un changement = une intention = une entrée dans le changelog + un export post-changement.
### Processus recommandé (léger)
1. Définir lobjectif (1 phrase).
2. Appliquer dans lUI.
3. Vérifier (section “Vérifications post-changement”).
4. Exporter la config.
5. Écrire 1 entrée dans le changelog.
---
## Vérifications post-changement (checklist)
### Accès / sécurité
- [ ] Accès admin local (LAN management) OK
- [ ] Accès distant (si applicable) OK
- [ ] Aucune règle “allow any any” accidentelle
- [ ] Journaux consultés (erreurs/deny inattendus)
### Réseau
- [ ] Ping vers passerelle
- [ ] Résolution DNS depuis LAN
- [ ] NTP synchronisé
- [ ] Routage inter-VLAN (si applicable)
- [ ] NAT/port forward (si applicable)
### Services
- [ ] DHCP délivre des baux (si activé)
- [ ] DNS (Unbound) répond (si activé)
- [ ] VPN up (si utilisé)
- [ ] IDS/IPS stable (si activé)
### Monitoring
- [ ] Checks externes (Icinga) au vert
- [ ] Latence/perte dans les seuils
---
## Mises à jour (upgrade)
### Avant
- [ ] Export de config
- [ ] Note dans changelog
- [ ] Fenêtre de maintenance
- [ ] Plan de rollback (version + backup)
### Après
- [ ] Vérifications post-changement
- [ ] Export de config (post-upgrade)
---
## Annexes (à compléter)
### Alias (liste)
- ALIAS_1 : ___________________
- ALIAS_2 : ___________________
### NAT / Port forwards (intention)
- Service A : ___________________
- Service B : ___________________
### VPN (intention)
- Site-to-site : ___________________
- Remote access : ___________________

View file

@ -0,0 +1,21 @@
# Global defaults / overrides
# Baseline
baseline_packages_enabled: true
baseline_updates_enabled: true
# Security
security_hardening_enabled: true
firewall_nftables_enabled: true
# Monitoring (unifié)
monitoring_icinga_enabled: true
monitoring_icinga_agent_enabled: true
monitoring_icinga_plugins_enabled: false
monitoring_icinga_director_enabled: false
# DNS (PowerDNS)
dns_powerdns_enabled: false
# Reverse proxy (Traefik)
reverse_proxy_traefik_enabled: false

View file

@ -0,0 +1,13 @@
all:
children:
servers:
hosts:
# exemple:
# srv1:
# ansible_host: 192.168.15.10
dns:
hosts: {}
proxy:
hosts: {}
proxmox:
hosts: {}

View file

@ -0,0 +1,53 @@
---
- name: Kernel hardening - sysctl
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Configure sysctl settings for kernel hardening
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
sysctl_set: yes
reload: yes
loop:
- { key: fs.suid_dumpable, value: 0 }
- { key: kernel.core_uses_pid, value: 1 }
- { key: kernel.dmesg_restrict, value: 1 }
- { key: kernel.kptr_restrict, value: 2 }
- { key: kernel.sysrq, value: 0 }
# - { key: net.ipv4.conf.all.forwarding, value: 0 }
- { key: net.ipv4.conf.all.log_martians, value: 1 }
- { key: net.ipv4.conf.all.rp_filter, value: 1 }
- { key: net.ipv4.conf.all.send_redirects, value: 0 }
- { key: net.ipv4.conf.default.accept_redirects, value: 0 }
- { key: net.ipv4.conf.default.accept_source_route, value: 0 }
- { key: net.ipv4.conf.default.log_martians, value: 1 }
- { key: net.ipv6.conf.all.accept_redirects, value: 0 }
- { key: net.ipv6.conf.default.accept_redirects, value: 0 }
- name: Ensure settings are persistent in /etc/sysctl.conf or drop-in
copy:
dest: "/etc/sysctl.d/99-hardening.conf"
content: |
fs.suid_dumpable = 0
kernel.core_uses_pid = 1
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.sysrq = 0
#net.ipv4.conf.all.forwarding = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.log_martians = 1
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
mode: '0644'
owner: root
group: root
- name: Reload sysctl to apply new settings
command: sysctl --system

View file

@ -0,0 +1,112 @@
version: '3.8'
services:
##########################################################
# 1) STEPCA - Autorité de certification ACME interne
##########################################################
stepca:
image: smallstep/step-ca:latest
container_name: stepca
environment:
STEPPATH: /home/step
volumes:
- ./step_data:/home/step
expose:
- '9000' # Port interne (ACME / API)
networks:
- back_net
##########################################################
# 2) KEYCLOAK
##########################################################
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: keycloak
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
command: >
start-dev
--http-relative-path /auth
expose:
- '8080'
networks:
- back_net
##########################################################
# 3) ERPLibre : MySQL (erplibre_db) + PHP-FPM (erplibre_php)
##########################################################
erplibre_db:
image: mysql:5.7
container_name: erplibre_db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: erplibre
MYSQL_USER: erplibre
MYSQL_PASSWORD: erplibre
command: >
--default-authentication-plugin=mysql_native_password
--collation-server=utf8mb4_unicode_ci
--character-set-server=utf8mb4
volumes:
- ./erplibre_db_data:/var/lib/mysql
networks:
- back_net
erplibre_php:
# Dans l'idéal, tu construis l'image depuis le code ERPLibre (backend).
# Sinon, s'ils fournissent une image Docker "prête à l'emploi", utilise-la ici.
build:
context: ./backend
container_name: erplibre_php
environment:
DB_HOST: erplibre_db
DB_DATABASE: erplibre
DB_USERNAME: erplibre
DB_PASSWORD: erplibre
expose:
- "9000"
depends_on:
- erplibre_db
networks:
- back_net
##########################################################
# 4) CERTBOT - pour récupérer des certs depuis StepCA
##########################################################
certbot:
image: certbot/certbot:latest
container_name: certbot
volumes:
- ./certs:/etc/letsencrypt
networks:
- back_net
# Pas d'expose: on l'invoque manuellement ou via cron
##########################################################
# 5) NGINX - Reverse Proxy unique pour tout
##########################################################
nginx:
image: nginx:latest
container_name: nginx_proxy
depends_on:
- stepca
- keycloak
- erplibre_php
- erplibre_db
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs
networks:
- front_net
- back_net
networks:
front_net:
driver: bridge
back_net:
driver: bridge

View file

@ -0,0 +1,42 @@
---
- name: Déployer Keycloak dans Docker
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Télécharger l'image Docker Keycloak
docker_image:
name: quay.io/keycloak/keycloak
tag: latest
source: pull
- name: Créer un répertoire pour les données de Keycloak
file:
path: /opt/keycloak
state: directory
mode: '0755'
- name: Exécuter le conteneur Keycloak
docker_container:
name: keycloak
image: quay.io/keycloak/keycloak:latest
state: started
restart_policy: always
ports:
- "8080:8080"
- "8443:8443"
volumes:
- /opt/keycloak:/opt/keycloak/data
env:
KEYCLOAK_ADMIN: "admin"
KEYCLOAK_ADMIN_PASSWORD: "admin"
command: start-dev
- name: Afficher l'état du conteneur Keycloak en cours d'exécution
command: docker ps
register: docker_ps_output
- name: Imprimer l'état de Keycloak
debug:
var: docker_ps_output.stdout

View file

@ -0,0 +1,39 @@
---
- name: Déployer Smallstep CA dans Docker
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Télécharger l'image Docker Smallstep CA
docker_image:
name: smallstep/step-ca
tag: latest
source: pull
- name: Créer un répertoire pour les données de Smallstep CA
file:
path: /opt/smallstep-ca
state: directory
mode: '0755'
- name: Exécuter le conteneur Smallstep CA
docker_container:
name: smallstep-ca
image: smallstep/step-ca:latest
state: started
restart_policy: always
ports:
- "9000:9000"
volumes:
- /opt/smallstep-ca:/home/step
env:
STEP_CA_PASSWORD: "changeit"
- name: Afficher l'état du conteneur Smallstep CA en cours d'exécution
command: docker ps
register: docker_ps_output
- name: Imprimer l'état de Smallstep CA
debug:
var: docker_ps_output.stdout

View file

@ -0,0 +1,57 @@
---
- name: Deploy Smallstep CA with Docker Compose
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Create the directory for Docker Compose
file:
path: /opt/smallstep
state: directory
mode: '0755'
- name: Create the secrets directory for Smallstep
file:
path: /opt/smallstep/secrets
state: directory
mode: '0700'
- name: Create the password file for Smallstep CA
copy:
dest: /opt/smallstep/secrets/password
content: "T0rp1n0uch3."
mode: '0600'
- name: Create docker-compose.yml for Smallstep CA
copy:
dest: /opt/smallstep/docker-compose.yml
content: |
version: '3.8'
services:
step-ca:
image: smallstep/step-ca:latest
container_name: step-ca
ports:
- "443:443"
environment:
DOCKER_STEPCA_INIT_NAME: "MoiJeLConnais"
DOCKER_STEPCA_INIT_DNS_NAMES: "example.com,api.example.com"
DOCKER_STEPCA_INIT_PASSWORD_FILE: "/home/step/secrets/password"
DOCKER_STEPCA_INIT_PROVISIONER_NAME: "admin"
DOCKER_STEPCA_INIT_PROVISIONER_PASSWORD_FILE: "/home/step/secrets/password"
volumes:
- "/opt/smallstep/data:/home/step"
- "/opt/smallstep/secrets:/home/step/secrets"
restart: always
mode: '0644'
- name: Install Docker Compose (if not already installed)
apt:
name: docker-compose
state: present
when: ansible_facts['os_family'] == "Debian"
- name: Start Smallstep CA with Docker Compose
command: docker-compose up -d
args:
chdir: /opt/smallstep

View file

@ -0,0 +1,11 @@
---
- name: Exécuter apt upgrade sur tous les serveurs
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Mettre à jour la liste des paquets APT
apt:
update_cache: yes

View file

@ -0,0 +1,567 @@
---
- name: Apply security hardening based on Linys recommendations
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Ensure UFW is installed
apt:
name: ufw
state: present
- name: Allow SSH immediately
command: ufw allow 22/tcp
- name: Set default policy to deny incoming traffic
command: ufw default deny incoming
- name: Set default policy to allow outgoing traffic
command: ufw default allow outgoing
- name: Allow HTTP and HTTPS
command: ufw allow {{ item }}/tcp
loop:
- 80
- 443
- name: Allow ICINGA2 ports
command: ufw allow 5665/tcp
- name: Allow OpenVPN port
command: ufw allow 1194/udp
- name: Enable UFW without confirmation
command: echo "y" | ufw enable
- name: Verify UFW rules
command: ufw status verbose
register: ufw_status
changed_when: false
- name: Display UFW configuration
debug:
msg: "{{ ufw_status.stdout }}"
# 4. Installation des mises à jour & des rustines
- name: Mettre à jour la liste des paquets APT
apt:
update_cache: yes
- name: Mettre à jour uniquement les paquets installés
apt:
upgrade: yes
# 5. Ensure correct file permissions on sensitive files
- name: Set permissions on /etc/passwd
file:
path: /etc/passwd
owner: root
group: root
mode: '0644'
- name: Set permissions on /etc/shadow
file:
path: /etc/shadow
owner: root
group: shadow
mode: '0640'
- name: Set permissions on /etc/group
file:
path: /etc/group
owner: root
group: root
mode: '0644'
- name: Set permissions on /etc/gshadow
file:
path: /etc/gshadow
owner: root
group: shadow
mode: '0640'
# 6. Disable core dumps (to prevent information leakage)
- name: Disable core dumps
lineinfile:
path: /etc/security/limits.conf
line: '* hard core 0'
state: present
- name: Ensure core dumps are disabled via sysctl
sysctl:
name: fs.suid_dumpable
value: '0'
state: present
sysctl_set: yes
# 7. Restrict cron jobs
- name: Restrict cron jobs to root and authorized users
file:
path: /etc/cron.deny
state: absent
- name: Ensure /etc/cron.allow exists and has correct permissions
file:
path: /etc/cron.allow
state: touch
owner: root
group: root
mode: '0640'
# 7. Améliorer la sécurité des répertoires temporaires
- name: Install libpam-tmpdir and apt-listbugs
apt:
name:
- libpam-tmpdir
state: present
# 8. Disable IPv6 if not in use
- name: Disable IPv6 via sysctl
sysctl:
name: net.ipv6.conf.all.disable_ipv6
value: '1'
state: present
sysctl_set: yes
reload: yes
# Adjust kernel and sysctl parameters for hardening
- name: Disable TTY line discipline autoload
sysctl:
name: dev.tty.ldisc_autoload
value: '0'
state: present
sysctl_set: yes
- name: Use PID in core dump filenames
sysctl:
name: kernel.core_uses_pid
value: '1'
state: present
sysctl_set: yes
# ATTENTION !!! IRRÉVERSIBLE !!! PAS TOUCHE AVANT SNAPSHOT !!!
#- name: Disable loading of kernel modules after boot
# sysctl:
# name: kernel.modules_disabled
# value: '1'
# state: present
# sysctl_set: yes
- name: Disable unprivileged BPF
sysctl:
name: kernel.unprivileged_bpf_disabled
value: '1'
state: present
sysctl_set: yes
- name: Restrict ptrace to parent processes only
sysctl:
name: kernel.yama.ptrace_scope
value: '1'
state: present
sysctl_set: yes
- name: Harden BPF JIT compilation
sysctl:
name: net.core.bpf_jit_harden
value: '2'
state: present
sysctl_set: yes
- name: Enable reverse path filtering for IPv4
sysctl:
name: net.ipv4.conf.all.rp_filter
value: '1'
state: present
sysctl_set: yes
- name: Disable sending of ICMP redirects for IPv4
sysctl:
name: net.ipv4.conf.all.send_redirects
value: '0'
state: present
sysctl_set: yes
- name: Disable accepting of ICMP redirects by default for IPv4
sysctl:
name: net.ipv4.conf.default.accept_redirects
value: '0'
state: present
sysctl_set: yes
- name: Enable logging of suspicious packets for default IPv4
sysctl:
name: net.ipv4.conf.default.log_martians
value: '1'
state: present
sysctl_set: yes
- name: Disable accepting of ICMP redirects by default for IPv6
sysctl:
name: net.ipv6.conf.default.accept_redirects
value: '0'
state: present
sysctl_set: yes
# 9. Ensure auditing is configured (auditd)
- name: Install auditd
apt:
name: auditd
state: present
- name: Ensure auditd service is enabled and running
service:
name: auditd
state: started
enabled: yes
# 12. Enable AppArmor (or SELinux on supported systems)
- name: Ensure AppArmor is enabled and running
service:
name: apparmor
state: started
enabled: yes
# 13. Create a banner for unauthorized access
- name: Create a message of the day (MOTD) banner
copy:
dest: /etc/motd
content: |
********************** AVERTISSEMENT ***********************
Ce système est strictement réservé aux utilisateurs autorisés
Toute tentative d'accès non autorisé est interdite et peut
être signalée aux autorités compétentes.
Votre activité peut être surveillée et enregistrée.
En continuant, vous acceptez ces conditions.
************************ WARNING ***************************
This system is strictly for authorized users only.
Any unauthorized access attempts are prohibited and may
be reported to the appropriate authorities.
Your activity may be monitored and logged.
By proceeding, you accept these terms.
owner: root
group: root
mode: '0644'
# 14. Apply SSH hardening configuration
- name: Backup the current sshd_config
copy:
src: /etc/ssh/sshd_config
dest: /etc/ssh/sshd_config.bak
owner: root
group: root
mode: '0644'
remote_src: yes
backup: yes
- name: Ensure banner is displayed on SSH login
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Banner'
line: 'Banner /etc/motd'
state: present
- name: Backup the current sshd_config
copy:
src: /etc/ssh/sshd_config
dest: /etc/ssh/sshd_config.bak
owner: root
group: root
mode: '0644'
remote_src: yes
backup: yes
- name: Ensure the PermitRootLogin is set to no (disable root login)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
state: present
- name: Ensure PasswordAuthentication is set to no (force key-based authentication)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
- name: Disable empty passwords
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitEmptyPasswords'
line: 'PermitEmptyPasswords no'
state: present
- name: Set MaxAuthTries to 3 (limit authentication attempts)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?MaxAuthTries'
line: 'MaxAuthTries 3'
state: present
- name: Set MaxSessions to 2 (limit open sessions)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?MaxSessions'
line: 'MaxSessions 2'
state: present
- name: Disable X11 forwarding
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?X11Forwarding'
line: 'X11Forwarding no'
state: present
- name: Disable TCP forwarding
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?AllowTcpForwarding'
line: 'AllowTcpForwarding no'
state: present
- name: Disable agent forwarding
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?AllowAgentForwarding'
line: 'AllowAgentForwarding no'
state: present
- name: Set ClientAliveInterval to 300 seconds (5 minutes)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?ClientAliveInterval'
line: 'ClientAliveInterval 300'
state: present
- name: Set ClientAliveCountMax to 0 (disconnect inactive sessions)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?ClientAliveCountMax'
line: 'ClientAliveCountMax 0'
state: present
- name: Set LoginGraceTime to 30 seconds
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?LoginGraceTime'
line: 'LoginGraceTime 30'
state: present
- name: Set UsePAM to yes
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?UsePAM'
line: 'UsePAM yes'
state: present
- name: Disable SSH version 1 (only use version 2)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Protocol'
line: 'Protocol 2'
state: present
- name: Disable DNS lookup for SSH (improve performance)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?UseDNS'
line: 'UseDNS no'
state: present
- name: Restart SSH service to apply changes
service:
name: sshd
state: restarted
# - name: Ensure apt-listbugs is installed
# apt:
# name: apt-listbugs
# state: present
- name: Ensure debsums is installed
apt:
name: debsums
state: present
- name: Ensure needrestart is installed
apt:
name: needrestart
state: present
# 14. Ensure log rotation is enabled and configured
- name: Ensure rsyslog is installed
apt:
name: rsyslog
state: present
- name: Ensure logrotate is installed
apt:
name: logrotate
state: present
- name: Create logrotate configuration for rsyslog
copy:
dest: /etc/logrotate.d/rsyslog
content: |
/var/log/syslog
{
rotate 7
daily
missingok
notifempty
delaycompress
compress
postrotate
/usr/lib/rsyslog/rsyslog-rotate > /dev/null 2>&1 || true
endscript
}
owner: root
group: root
mode: '0644'
- name: Verify logrotate configuration
shell: logrotate -d /etc/logrotate.conf
register: logrotate_test
ignore_errors: yes
- name: Debug logrotate output
debug:
var: logrotate_test.stdout
# 1. Update sysctl parameters
- name: Apply recommended sysctl settings
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: yes
with_items:
- { key: "fs.protected_fifos", value: "2" }
- { key: "kernel.kptr_restrict", value: "2" }
- { key: "kernel.sysrq", value: "0" }
- { key: "net.ipv4.conf.all.accept_redirects", value: "0" }
- { key: "net.ipv4.conf.all.log_martians", value: "1" }
- { key: "net.ipv4.tcp_syncookies", value: "1" }
- { key: "net.ipv6.conf.all.accept_redirects", value: "0" }
# 5. Install fail2ban
- name: Install fail2ban
package:
name: fail2ban
state: present
- name: Ensure fail2ban service is running
service:
name: fail2ban
state: started
enabled: true
# 6. Install and configure Chkrootkit
- name: Ensure chkrootkit is installed
apt:
name: chkrootkit
state: present
update_cache: yes
- name: Create directory for chkrootkit logs
file:
path: /var/log/chkrootkit
state: directory
mode: '0755'
- name: Create chkrootkit daily scan script
copy:
dest: /etc/cron.daily/chkrootkit
content: |
#!/bin/bash
LOGFILE="/var/log/chkrootkit/chkrootkit.log"
echo "Chkrootkit scan on $(date)" > "$LOGFILE"
chkrootkit >> "$LOGFILE" 2>&1
owner: root
group: root
mode: '0755'
- name: Run immediate chkrootkit scan
command: chkrootkit
register: chkrootkit_scan
- name: Debug chkrootkit scan results
debug:
var: chkrootkit_scan.stdout
# 5. Install lynis & auditer la machine
- name: Installer Lynis
apt:
name: lynis
state: present
- name: Exécuter un audit de sécurité
command: lynis audit system
register: lynis_output
- name: Afficher le résultat de l'audit Lynis
debug:
var: lynis_output.stdout
# 6. Full AIDE setup from scratch
- name: Install AIDE package
ansible.builtin.package:
name: aide
state: present
- name: Deploy AIDE configuration file
ansible.builtin.copy:
content: |
database=file:/var/lib/aide/aide.db.gz
database_out=file:/var/lib/aide/aide.db.new.gz
gzip_dbout=yes
# Define groups for rules
NORMAL = p+i+n+u+g+s+m+c+acl+selinux+sha256
F = sha256+ftype
# Directories and files to monitor
/etc NORMAL
/bin NORMAL
/sbin NORMAL
/usr/bin NORMAL
/usr/sbin NORMAL
/var/log F
dest: /etc/aide/aide.conf
owner: root
group: root
mode: '0644'
- name: Remove any existing AIDE database (if any)
ansible.builtin.file:
path: /var/lib/aide/aide.db.gz
state: absent
- name: Initialize AIDE database
command: aide --init --config=/etc/aide/aide.conf
args:
creates: /var/lib/aide/aide.db.new.gz
- name: Replace old AIDE database with the new one
command: mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
args:
creates: /var/lib/aide/aide.db.gz
- name: Schedule AIDE periodic checks with cron
ansible.builtin.cron:
name: "AIDE periodic integrity check"
user: root
job: "/usr/bin/aide --check --config=/etc/aide/aide.conf"
special_time: daily

View file

@ -0,0 +1,68 @@
---
- name: Add grouped Python plugins as CheckCommands in Icinga Director
hosts: leChoixDuSysadmin
gather_facts: no
vars:
# Grouped plugins by functionality
plugin_groups:
security_network:
- name: "001_ensure_ufw_is_installed"
path: "/usr/lib/nagios/plugins/001_ensure_ufw_is_installed.py"
- name: "002_allow_ssh_immediately"
path: "/usr/lib/nagios/plugins/002_allow_ssh_immediately.py"
- name: "003_set_default_policy_to_deny_incoming_traffic"
path: "/usr/lib/nagios/plugins/003_set_default_policy_to_deny_incoming_traffic.py"
permissions_access:
- name: "013_set_permissions_on_etc_passwd"
path: "/usr/lib/nagios/plugins/013_set_permissions_on_etc_passwd.py"
- name: "014_set_permissions_on_etc_shadow"
path: "/usr/lib/nagios/plugins/014_set_permissions_on_etc_shadow.py"
ssh_config:
- name: "040_ensure_the_permitrootlogin_is_set_to_no_disable_root_login"
path: "/usr/lib/nagios/plugins/040_ensure_the_permitrootlogin_is_set_to_no_disable_root_login.py"
- name: "041_ensure_passwordauthentication_is_set_to_no_force_key_based_authentication"
path: "/usr/lib/nagios/plugins/041_ensure_passwordauthentication_is_set_to_no_force_key_based_authentication.py"
audits_logs:
- name: "033_install_auditd"
path: "/usr/lib/nagios/plugins/033_install_auditd.py"
- name: "034_ensure_auditd_service_is_enabled_and_running"
path: "/usr/lib/nagios/plugins/034_ensure_auditd_service_is_enabled_and_running.py"
system_settings:
- name: "011_mettre_jour_la_liste_des_paquets_apt"
path: "/usr/lib/nagios/plugins/011_mettre_jour_la_liste_des_paquets_apt.py"
- name: "012_mettre_jour_uniquement_les_paquets_install_s"
path: "/usr/lib/nagios/plugins/012_mettre_jour_uniquement_les_paquets_install_s.py"
backups_banners:
- name: "036_create_a_message_of_the_day_motd_banner"
path: "/usr/lib/nagios/plugins/036_create_a_message_of_the_day_motd_banner.py"
- name: "037_backup_the_current_sshd_config"
path: "/usr/lib/nagios/plugins/037_backup_the_current_sshd_config.py"
security_integrity:
- name: "061_install_fail2ban"
path: "/usr/lib/nagios/plugins/061_install_fail2ban.py"
- name: "062_ensure_fail2ban_service_is_running"
path: "/usr/lib/nagios/plugins/062_ensure_fail2ban_service_is_running.py"
tasks:
- name: Add CheckCommands for each group of plugins
uri:
url: "https://adm-kbr.delaviorne.net/api/v1/checkcommand"
method: POST
headers:
Accept: "application/json"
Authorization: "Bearer your_api_token"
body:
object_name: "{{ item.name }}"
attrs:
command: "{{ item.path }}"
templates: ["configAudit"]
vars.agent: true
body_format: json
validate_certs: no
with_items: "{{ plugin_groups | combine }}"
register: results
- name: Debug results
debug:
var: results

View file

@ -0,0 +1,37 @@
---
- name: Deploy and configure autogrow service on Debian 12.9
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Ensure /usr/local/bin directory exists
file:
path: /usr/local/bin
state: directory
mode: '0755'
- name: Copy autogrow script to /usr/local/bin
copy:
src: fichiers/autogrow.sh
dest: /usr/local/bin/autogrow.sh
mode: '0755'
- name: Copy autogrow systemd service file
copy:
src: fichiers/autogrow.service
dest: /etc/systemd/system/autogrow.service
mode: '0644'
- name: Reload systemd daemon
command: systemctl daemon-reload
- name: Enable autogrow service
systemd:
name: autogrow.service
enabled: yes
- name: Start autogrow service
systemd:
name: autogrow.service
state: started

View file

@ -0,0 +1,55 @@
---
- name: Install Docker on Debian 12
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Update the package index
apt:
update_cache: yes
- name: Install required packages for Docker
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- software-properties-common
state: present
- name: Add Docker GPG key
ansible.builtin.shell: |
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
args:
creates: /usr/share/keyrings/docker-archive-keyring.gpg
- name: Add Docker repository
ansible.builtin.copy:
content: "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bookworm stable\n"
dest: /etc/apt/sources.list.d/docker.list
- name: Update the package index after adding Docker repository
apt:
update_cache: yes
- name: Install Docker packages
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
state: present
- name: Ensure Docker service is enabled and running
systemd:
name: docker
enabled: true
state: started
- name: Add current user to Docker group (optional)
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: yes
state: present

View file

@ -0,0 +1,23 @@
---
- name: Installer easy-rsa, copier le script easypki et créer la PKI Projet_KBR
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Assurer que le paquet easy-rsa est installé
apt:
name: easy-rsa
state: present
update_cache: yes
- name: Copier le script easypki vers /usr/sbin
copy:
src: ../pki/easypki
dest: /usr/sbin/easypki
mode: '0755'
- name: Créer la PKI Projet_KBR dans /var/lib si elle n'existe pas déjà
command: "/usr/sbin/easypki Projet_KBR"
args:
chdir: /var/lib
creates: /var/lib/Projet_KBR

View file

@ -0,0 +1,152 @@
---
- name: Configure Firewall with iptables securely
hosts: leChoixDuSysadmin
become: true
vars_files:
- iptables_config.yml
tasks:
- name: Installer iptables-persistent et auditd
apt:
name:
- iptables-persistent
- auditd
state: present
update_cache: yes
- name: Appliquer les règles INPUT
iptables:
chain: INPUT
protocol: "{{ item.protocol }}"
destination_port: "{{ item.port }}"
source: "{{ item.source | default(omit) }}"
jump: "{{ item.action }}"
loop: "{{ rules_input }}"
- name: Appliquer les règles FORWARD
iptables:
chain: FORWARD
source: "{{ item.source }}"
destination: "{{ item.destination }}"
jump: "{{ item.action }}"
loop: "{{ rules_forward }}"
- name: Appliquer les règles OUTPUT
iptables:
chain: OUTPUT
protocol: "{{ item.protocol }}"
destination_port: "{{ item.port }}"
jump: "{{ item.action }}"
loop: "{{ rules_output }}"
- name: Activer le NAT pour Docker (Masquerading)
iptables:
table: nat
chain: POSTROUTING
source: "{{ item.source }}"
destination: "{{ item.destination }}"
jump: "{{ item.action }}"
loop: "{{ rules_nat_masquerade }}"
- name: Ajouter les règles de port forwarding (DNAT)
iptables:
table: nat
chain: PREROUTING
protocol: "{{ item.proto }}"
destination_port: "{{ item.ext_port }}"
to_destination: "{{ item.int_ip }}:{{ item.int_port }}"
jump: DNAT
loop: "{{ rules_nat_dnat }}"
# 🔐 Protection contre brute-force SSH
- name: Limiter les connexions SSH à 3 tentatives par minute
iptables:
chain: INPUT
protocol: tcp
destination_port: 22
match: limit
limit: "3/min"
jump: ACCEPT
# 🔐 Protection SYN Flood (DDoS)
- name: Protection SYN Flood
iptables:
chain: INPUT
protocol: tcp
match: limit
limit: "10/s"
limit_burst: 20
jump: ACCEPT
- name: Autoriser le trafic loopback
iptables:
chain: INPUT
in_interface: lo
jump: ACCEPT
- name: Autoriser le trafic loopback (OUTPUT)
iptables:
chain: OUTPUT
out_interface: lo
jump: ACCEPT
- name: Autoriser les connexions déjà établies
iptables:
chain: "{{ item }}"
match: conntrack
ctstate: RELATED,ESTABLISHED
jump: ACCEPT
loop:
- INPUT
- FORWARD
- OUTPUT
# 📢 LOGGING 🔥
- name: Loguer le trafic bloqué en INPUT
iptables:
chain: INPUT
jump: LOG
log_prefix: "INPUT DROP: "
log_level: 4
- name: Loguer le trafic bloqué en FORWARD
iptables:
chain: FORWARD
jump: LOG
log_prefix: "FORWARD DROP: "
log_level: 4
- name: Loguer le trafic bloqué en OUTPUT
iptables:
chain: OUTPUT
jump: LOG
log_prefix: "OUTPUT DROP: "
log_level: 4
# 🚫 BLOQUER TOUT LE RESTE
- name: Bloquer tout autre trafic entrant
iptables:
chain: INPUT
jump: DROP
- name: Bloquer tout autre trafic forward
iptables:
chain: FORWARD
jump: DROP
- name: Bloquer tout autre trafic sortant
iptables:
chain: OUTPUT
jump: DROP
# 🔍 Surveillance des modifications d'iptables
#- name: Activer laudit des modifications diptables
# shell: "auditctl -w /sbin/iptables -p x -k firewall_change"
- name: Sauvegarder les règles iptables
shell: iptables-save > /etc/iptables/rules.v4
- name: Rendre les règles persistantes
command: netfilter-persistent save

View file

@ -0,0 +1,116 @@
- name: Installation et configuration de Mailcow sur Debian 12.9
hosts: leChoixDuSysadmin
become: yes
vars:
mailcow_dir: "/opt/mailcow"
mailcow_domain: "mail.exemple.com"
tasks:
- name: Mettre à jour le système
apt:
update_cache: yes
upgrade: dist
- name: Installer les dépendances nécessaires
apt:
name:
- curl
- git
- sudo
- ufw
- unzip
- wget
- bash-completion
- dnsutils
- python3-pip
state: present
- name: Désactiver systemd-resolved
systemd:
name: systemd-resolved
enabled: no
state: stopped
- name: Supprimer resolv.conf et définir un nouveau DNS
file:
path: /etc/resolv.conf
state: absent
- name: Configurer un DNS de secours
copy:
dest: /etc/resolv.conf
content: "nameserver 8.8.8.8\n"
- name: Vérifier si Mailcow est déjà installé
stat:
path: "{{ mailcow_dir }}/docker-compose.yml"
register: mailcow_installed
- name: Cloner le dépôt Mailcow
git:
repo: "https://github.com/mailcow/mailcow-dockerized.git"
dest: "{{ mailcow_dir }}"
version: "master"
when: not mailcow_installed.stat.exists
# - name: Générer le fichier de configuration Mailcow
# command:
# cmd: "./generate_config.sh"
# chdir: "{{ mailcow_dir }}"
# - name: Vérifier si le fichier mailcow.conf existe
# stat:
# path: "{{ mailcow_dir }}/mailcow.conf"
# register: mailcow_config
# - name: Créer le fichier mailcow.conf s'il n'existe pas
# copy:
# dest: "{{ mailcow_dir }}/mailcow.conf"
# content: "MAILCOW_HOSTNAME={{ mailcow_domain }}\nDISABLE_IPV6=y\n"
# when: not mailcow_config.stat.exists
- name: Télécharger les images Docker Mailcow
command:
cmd: "docker compose pull"
chdir: "{{ mailcow_dir }}"
- name: Démarrer Mailcow avec Docker Compose
command:
cmd: "docker compose up -d"
chdir: "{{ mailcow_dir }}"
- name: Ouvrir les ports nécessaires avec UFW
ufw:
rule: leChoixDuSysadminow
port: "{{ item }}"
proto: tcp
loop:
- "22"
- "80"
- "443"
- "25"
- "465"
- "587"
- "993"
- "995"
- name: Activer UFW
ufw:
state: enabled
- name: Redémarrer le conteneur acme-mailcow pour générer les certificats SSL
command:
cmd: "docker compose restart acme-mailcow"
chdir: "{{ mailcow_dir }}"
- name: Générer la clé DKIM et afficher la configuration DNS
command:
cmd: "docker compose exec -T rspamd-mailcow rspamadm dkim_keygen -d {{ mailcow_domain }}"
chdir: "{{ mailcow_dir }}"
register: dkim_output
- name: Afficher la clé DKIM à ajouter dans le DNS
debug:
msg: "{{ dkim_output.stdout }}"

View file

@ -0,0 +1,65 @@
---
- name: Installer et configurer NGINX avec Certbot
hosts: leChoixDuSysadmin
become: yes # Exécution avec sudo
vars:
domain_name: "change.me" # À remplacer par votre domaine
email: "someone@somewhere.lol" # Adresse e-mail pour Let's Encrypt
nginx_conf_path: "/etc/nginx/sites-available/{{ domain_name }}"
nginx_conf_link: "/etc/nginx/sites-enabled/{{ domain_name }}"
tasks:
- name: Mettre à jour la liste des paquets
apt:
update_cache: yes
- name: Installer NGINX et Certbot
apt:
name:
- nginx
- certbot
- python3-certbot-nginx
state: present
- name: Créer un répertoire pour le site web
file:
path: "/var/www/{{ domain_name }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Déployer la configuration NGINX
template:
src: templates/nginx_vhost.conf.j2
dest: "{{ nginx_conf_path }}"
notify: Redémarrer NGINX
- name: Activer le site en créant un lien symbolique
file:
src: "{{ nginx_conf_path }}"
dest: "{{ nginx_conf_link }}"
state: link
notify: Redémarrer NGINX
# - name: Vérifier que NGINX fonctionne avant la demande de certificat
#uri:
#url: "http://{{ domain_name }}"
#status_code: 200
#register: web_status
#until: web_status.status == 200
#retries: 5
#delay: 5
- name: Obtenir un certificat SSL avec Certbot
command: "certbot --nginx -d {{ domain_name }} --email {{ email }} --non-interactive --agree-tos"
args:
creates: "/etc/letsencrypt/live/{{ domain_name }}/fullchain.pem"
notify: Redémarrer NGINX
handlers:
- name: Redémarrer NGINX
systemd:
name: nginx
state: restarted
enabled: yes

View file

@ -0,0 +1,77 @@
---
- name: Installer les prérequis pour OpenVPN et Easy-RSA
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Mettre à jour la liste des paquets
apt:
update_cache: yes
- name: Installer OpenVPN et Easy-RSA
apt:
name:
- openvpn
- easy-rsa
state: present
- name: Vérifier l'existence du script Easy-RSA
stat:
path: /usr/share/easy-rsa/easyrsa
register: easyrsa_stat
- name: Créer le répertoire Easy-RSA dans OpenVPN
file:
path: /etc/openvpn/easy-rsa
state: directory
owner: root
group: root
mode: '0755'
- name: Supprimer l'ancien fichier vars s'il existe
file:
path: /etc/openvpn/easy-rsa/vars
state: absent
- name: Initialiser l'environnement PKI sans confirmation
command: "echo yes | /usr/share/easy-rsa/easyrsa init-pki"
args:
chdir: /etc/openvpn/easy-rsa
when: easyrsa_stat.stat.exists
- name: Générer le CA
command: "echo yes | /usr/share/easy-rsa/easyrsa build-ca nopass"
args:
chdir: /etc/openvpn/easy-rsa
when: easyrsa_stat.stat.exists
- name: Générer la clé Diffie-Hellman
command: "echo yes | /usr/share/easy-rsa/easyrsa gen-dh"
args:
chdir: /etc/openvpn/easy-rsa
when: easyrsa_stat.stat.exists
- name: Créer un fichier de configuration serveur
copy:
dest: /etc/openvpn/server.conf
content: |
port 1194
proto udp
dev tun
ca /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
key /etc/openvpn/easy-rsa/pki/private/server.key
dh /etc/openvpn/easy-rsa/pki/dh.pem
server 10.8.0.0 255.255.255.0
keepalive 10 120
persist-key
persist-tun
status /var/log/openvpn-status.log
verb 3
- name: Activer et démarrer OpenVPN
systemd:
name: openvpn
enabled: yes
state: started

View file

@ -0,0 +1,515 @@
---
- name: Apply security hardening based on Linys recommendations
hosts: leChoixDuSysadmin
become: yes
tasks:
# 4. Installation des mises à jour & des rustines
- name: Mettre à jour la liste des paquets APT
apt:
update_cache: yes
- name: Mettre à jour uniquement les paquets installés
apt:
upgrade: yes
# 5. Ensure correct file permissions on sensitive files
- name: Set permissions on /etc/passwd
file:
path: /etc/passwd
owner: root
group: root
mode: '0644'
- name: Set permissions on /etc/shadow
file:
path: /etc/shadow
owner: root
group: shadow
mode: '0640'
- name: Set permissions on /etc/group
file:
path: /etc/group
owner: root
group: root
mode: '0644'
- name: Set permissions on /etc/gshadow
file:
path: /etc/gshadow
owner: root
group: shadow
mode: '0640'
# 6. Disable core dumps (to prevent information leakage)
- name: Disable core dumps
lineinfile:
path: /etc/security/limits.conf
line: '* hard core 0'
state: present
- name: Ensure core dumps are disabled via sysctl
sysctl:
name: fs.suid_dumpable
value: '0'
state: present
sysctl_set: yes
# 7. Restrict cron jobs
- name: Restrict cron jobs to root and authorized users
file:
path: /etc/cron.deny
state: absent
- name: Ensure /etc/cron.allow exists and has correct permissions
file:
path: /etc/cron.allow
state: touch
owner: root
group: root
mode: '0640'
# 7. Améliorer la sécurité des répertoires temporaires
- name: Install libpam-tmpdir and apt-listbugs
apt:
name:
- libpam-tmpdir
state: present
# 8. Disable IPv6 if not in use
- name: Disable IPv6 via sysctl
sysctl:
name: net.ipv6.conf.all.disable_ipv6
value: '1'
state: present
sysctl_set: yes
reload: yes
# Adjust kernel and sysctl parameters for hardening
- name: Disable TTY line discipline autoload
sysctl:
name: dev.tty.ldisc_autoload
value: '0'
state: present
sysctl_set: yes
- name: Use PID in core dump filenames
sysctl:
name: kernel.core_uses_pid
value: '1'
state: present
sysctl_set: yes
# ATTENTION !!! IRRÉVERSIBLE !!! PAS TOUCHE AVANT SNAPSHOT !!!
#- name: Disable loading of kernel modules after boot
# sysctl:
# name: kernel.modules_disabled
# value: '1'
# state: present
# sysctl_set: yes
- name: Disable unprivileged BPF
sysctl:
name: kernel.unprivileged_bpf_disabled
value: '1'
state: present
sysctl_set: yes
- name: Restrict ptrace to parent processes only
sysctl:
name: kernel.yama.ptrace_scope
value: '1'
state: present
sysctl_set: yes
- name: Harden BPF JIT compilation
sysctl:
name: net.core.bpf_jit_harden
value: '2'
state: present
sysctl_set: yes
- name: Enable reverse path filtering for IPv4
sysctl:
name: net.ipv4.conf.all.rp_filter
value: '1'
state: present
sysctl_set: yes
- name: Disable sending of ICMP redirects for IPv4
sysctl:
name: net.ipv4.conf.all.send_redirects
value: '0'
state: present
sysctl_set: yes
- name: Disable accepting of ICMP redirects by default for IPv4
sysctl:
name: net.ipv4.conf.default.accept_redirects
value: '0'
state: present
sysctl_set: yes
- name: Enable logging of suspicious packets for default IPv4
sysctl:
name: net.ipv4.conf.default.log_martians
value: '1'
state: present
sysctl_set: yes
- name: Disable accepting of ICMP redirects by default for IPv6
sysctl:
name: net.ipv6.conf.default.accept_redirects
value: '0'
state: present
sysctl_set: yes
# 9. Ensure auditing is configured (auditd)
- name: Install auditd
apt:
name: auditd
state: present
- name: Ensure auditd service is enabled and running
service:
name: auditd
state: started
enabled: yes
# 12. Enable AppArmor (or SELinux on supported systems)
- name: Ensure AppArmor is enabled and running
service:
name: apparmor
state: started
enabled: yes
# 13. Create a banner for unauthorized access
- name: Create a message of the day (MOTD) banner
copy:
dest: /etc/motd
content: |
********************** AVERTISSEMENT ***********************
Ce système est strictement réservé aux utilisateurs autorisés
Toute tentative d'accès non autorisé est interdite et peut
être signalée aux autorités compétentes.
Votre activité peut être surveillée et enregistrée.
En continuant, vous acceptez ces conditions.
************************ WARNING ***************************
This system is strictly for authorized users only.
Any unauthorized access attempts are prohibited and may
be reported to the appropriate authorities.
Your activity may be monitored and logged.
By proceeding, you accept these terms.
owner: root
group: root
mode: '0644'
# 14. Apply SSH hardening configuration
- name: Backup the current sshd_config
copy:
src: /etc/ssh/sshd_config
dest: /etc/ssh/sshd_config.bak
owner: root
group: root
mode: '0644'
remote_src: yes
backup: yes
- name: Ensure banner is displayed on SSH login
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Banner'
line: 'Banner /etc/motd'
state: present
- name: Backup the current sshd_config
copy:
src: /etc/ssh/sshd_config
dest: /etc/ssh/sshd_config.bak
owner: root
group: root
mode: '0644'
remote_src: yes
backup: yes
- name: Ensure the PermitRootLogin is set to no (disable root login)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
state: present
- name: Ensure PasswordAuthentication is set to no (force key-based authentication)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
- name: Disable empty passwords
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitEmptyPasswords'
line: 'PermitEmptyPasswords no'
state: present
- name: Set MaxAuthTries to 3 (limit authentication attempts)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?MaxAuthTries'
line: 'MaxAuthTries 3'
state: present
- name: Set MaxSessions to 2 (limit open sessions)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?MaxSessions'
line: 'MaxSessions 2'
state: present
- name: Disable X11 forwarding
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?X11Forwarding'
line: 'X11Forwarding no'
state: present
- name: Disable TCP forwarding
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?AllowTcpForwarding'
line: 'AllowTcpForwarding no'
state: present
- name: Disable agent forwarding
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?AllowAgentForwarding'
line: 'AllowAgentForwarding no'
state: present
- name: Set ClientAliveInterval to 300 seconds (5 minutes)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?ClientAliveInterval'
line: 'ClientAliveInterval 300'
state: present
- name: Set ClientAliveCountMax to 0 (disconnect inactive sessions)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?ClientAliveCountMax'
line: 'ClientAliveCountMax 0'
state: present
- name: Set LoginGraceTime to 30 seconds
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?LoginGraceTime'
line: 'LoginGraceTime 30'
state: present
- name: Set UsePAM to yes
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?UsePAM'
line: 'UsePAM yes'
state: present
- name: Disable SSH version 1 (only use version 2)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Protocol'
line: 'Protocol 2'
state: present
- name: Disable DNS lookup for SSH (improve performance)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?UseDNS'
line: 'UseDNS no'
state: present
- name: Restart SSH service to apply changes
service:
name: sshd
state: restarted
# 14. Ensure log rotation is enabled and configured
- name: Ensure rsyslog is installed
apt:
name: rsyslog
state: present
- name: Ensure logrotate is installed
apt:
name: logrotate
state: present
- name: Create logrotate configuration for rsyslog
copy:
dest: /etc/logrotate.d/rsyslog
content: |
/var/log/syslog
{
rotate 7
daily
missingok
notifempty
delaycompress
compress
postrotate
/usr/lib/rsyslog/rsyslog-rotate > /dev/null 2>&1 || true
endscript
}
owner: root
group: root
mode: '0644'
- name: Verify logrotate configuration
shell: logrotate -d /etc/logrotate.conf
register: logrotate_test
ignore_errors: yes
- name: Debug logrotate output
debug:
var: logrotate_test.stdout
# 1. Update sysctl parameters
- name: Apply recommended sysctl settings
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: yes
with_items:
- { key: "fs.protected_fifos", value: "2" }
- { key: "kernel.kptr_restrict", value: "2" }
- { key: "kernel.sysrq", value: "0" }
- { key: "net.ipv4.conf.all.accept_redirects", value: "0" }
- { key: "net.ipv4.conf.all.log_martians", value: "1" }
- { key: "net.ipv4.tcp_syncookies", value: "1" }
- { key: "net.ipv6.conf.all.accept_redirects", value: "0" }
# 5. Install fail2ban
- name: Install fail2ban
package:
name: fail2ban
state: present
- name: Ensure fail2ban service is running
service:
name: fail2ban
state: started
enabled: true
# 6. Install and configure Chkrootkit
- name: Ensure chkrootkit is installed
apt:
name: chkrootkit
state: present
update_cache: yes
- name: Create directory for chkrootkit logs
file:
path: /var/log/chkrootkit
state: directory
mode: '0755'
- name: Create chkrootkit daily scan script
copy:
dest: /etc/cron.daily/chkrootkit
content: |
#!/bin/bash
LOGFILE="/var/log/chkrootkit/chkrootkit.log"
echo "Chkrootkit scan on $(date)" > "$LOGFILE"
chkrootkit >> "$LOGFILE" 2>&1
owner: root
group: root
mode: '0755'
- name: Run immediate chkrootkit scan
command: chkrootkit
register: chkrootkit_scan
- name: Debug chkrootkit scan results
debug:
var: chkrootkit_scan.stdout
# 5. Install lynis & auditer la machine
- name: Installer Lynis
apt:
name: lynis
state: present
- name: Exécuter un audit de sécurité
command: lynis audit system
register: lynis_output
- name: Afficher le résultat de l'audit Lynis
debug:
var: lynis_output.stdout
# 6. Full AIDE setup from scratch
- name: Install AIDE package
ansible.builtin.package:
name: aide
state: present
- name: Deploy AIDE configuration file
ansible.builtin.copy:
content: |
database=file:/var/lib/aide/aide.db.gz
database_out=file:/var/lib/aide/aide.db.new.gz
gzip_dbout=yes
# Define groups for rules
NORMAL = p+i+n+u+g+s+m+c+acl+selinux+sha256
F = sha256+ftype
# Directories and files to monitor
/etc NORMAL
/bin NORMAL
/sbin NORMAL
/usr/bin NORMAL
/usr/sbin NORMAL
/var/log F
dest: /etc/aide/aide.conf
owner: root
group: root
mode: '0644'
- name: Remove any existing AIDE database (if any)
ansible.builtin.file:
path: /var/lib/aide/aide.db.gz
state: absent
- name: Initialize AIDE database
command: aide --init --config=/etc/aide/aide.conf
args:
creates: /var/lib/aide/aide.db.new.gz
- name: Replace old AIDE database with the new one
command: mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
args:
creates: /var/lib/aide/aide.db.gz
- name: Schedule AIDE periodic checks with cron
ansible.builtin.cron:
name: "AIDE periodic integrity check"
user: root
job: "/usr/bin/aide --check --config=/etc/aide/aide.conf"
special_time: daily

View file

@ -0,0 +1,130 @@
- name: Installation et Configuration de Step-CA
hosts: leChoixDuSysadmin
become: yes
vars:
stepca_user: step
stepca_group: step
stepca_home: /home/step
stepca_data_dir: /home/step/.step
stepca_service: step-ca
stepca_port: 9000
stepca_domain: "certificats.delaviorne.net"
stepca_init_password: "ChangeMe123!" # À CHANGER
stepca_provisioner: "admin"
tasks:
##################################################################
# 1⃣ Installation de Step-CA et Step-CLI
##################################################################
- name: Télécharger et installer Step-CLI et Step-CA (Ubuntu/Debian)
apt:
deb: "{{ item }}"
with_items:
- "https://dl.smallstep.com/cli/docs-ca-install/latest/step-cli_amd64.deb"
- "https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_amd64.deb"
when: ansible_os_family == "Debian"
- name: Télécharger et installer Step-CLI et Step-CA (CentOS/RHEL)
yum:
name: "{{ item }}"
state: present
with_items:
- "https://dl.smallstep.com/gh-release/certificates/pkg/latest/step-cli_amd64.rpm"
- "https://dl.smallstep.com/gh-release/certificates/pkg/latest/step-ca_amd64.rpm"
when: ansible_os_family == "RedHat"
- name: Vérifier l'installation de Step-CA et Step-CLI
command: step version
register: step_version
changed_when: false
##################################################################
# 2⃣ Création de l'utilisateur Step-CA
##################################################################
- name: Créer l'utilisateur Step-CA
user:
name: "{{ stepca_user }}"
comment: "Utilisateur pour Step-CA"
home: "{{ stepca_home }}"
shell: /bin/false
createhome: yes
- name: Créer le dossier de configuration Step-CA
file:
path: "{{ stepca_data_dir }}"
state: directory
owner: "{{ stepca_user }}"
group: "{{ stepca_group }}"
mode: '0755'
##################################################################
# 3⃣ Initialisation de Step-CA
##################################################################
- name: Vérifier si Step-CA est déjà initialisé
stat:
path: "{{ stepca_data_dir }}/ca.json"
register: ca_json
- name: Initialiser Step-CA (si nécessaire)
command: step ca init --name "My Internal CA" --dns "{{ stepca_domain }}" --address ":{{ stepca_port }}" --provisioner "{{ stepca_provisioner }}" --password-file /tmp/stepca_pass
args:
creates: "{{ stepca_data_dir }}/ca.json"
become_user: "{{ stepca_user }}"
when: not ca_json.stat.exists
- name: Supprimer le fichier de mot de passe temporaire
file:
path: /tmp/stepca_pass
state: absent
##################################################################
# 4⃣ Création du service systemd
##################################################################
- name: Configurer Step-CA comme un service systemd
copy:
dest: "/etc/systemd/system/{{ stepca_service }}.service"
mode: '0644'
content: |
[Unit]
Description=Step CA Service
After=network.target
[Service]
ExecStart=/usr/local/bin/step-ca {{ stepca_data_dir }}/ca.json
Restart=always
User={{ stepca_user }}
Group={{ stepca_group }}
Environment=STEPPATH={{ stepca_data_dir }}
WorkingDirectory={{ stepca_data_dir }}
[Install]
WantedBy=multi-user.target
- name: Recharger systemd
command: systemctl daemon-reload
- name: Activer et démarrer Step-CA
systemd:
name: "{{ stepca_service }}"
enabled: yes
state: started
##################################################################
# 5⃣ Vérification du service
##################################################################
- name: Vérifier que Step-CA tourne bien
shell: "systemctl is-active {{ stepca_service }}"
register: stepca_status
failed_when: stepca_status.stdout != "active"
changed_when: false
- name: Tester si Step-CA répond sur le port {{ stepca_port }}
uri:
url: "https://localhost:{{ stepca_port }}/acme/acme-directory"
method: GET
status_code: 200
validate_certs: no
register: stepca_test
failed_when: stepca_test.status != 200
changed_when: false

View file

@ -0,0 +1,140 @@
- name: Installation et Configuration de Step-CA
hosts: leChoixDuSysadmin
become: yes
vars:
stepca_user: step
stepca_group: step
stepca_home: /home/step
stepca_data_dir: /home/step/.step
stepca_tmp_dir: /var/tmp/.ansible_tmp
stepca_service: step-ca
stepca_port: 9000
stepca_domain: "certificats.delaviorne.net"
stepca_init_password: "T0rp1n0uch3." # À CHANGER
stepca_provisioner: "admin"
tasks:
##################################################################
# 1⃣ Création des répertoires nécessaires
##################################################################
- name: Créer le répertoire temporaire dAnsible (si `noexec` sur /tmp)
file:
path: "{{ stepca_tmp_dir }}"
state: directory
mode: '1777'
- name: Créer l'utilisateur Step-CA s'il n'existe pas
user:
name: "{{ stepca_user }}"
comment: "Utilisateur pour Step-CA"
home: "{{ stepca_home }}"
shell: /bin/bash
createhome: yes
- name: Créer le répertoire de configuration Step-CA
file:
path: "{{ stepca_data_dir }}"
state: directory
owner: "{{ stepca_user }}"
group: "{{ stepca_group }}"
mode: '0755'
- name: Assurer que Step-CA peut écrire dans son dossier
command: chown -R {{ stepca_user }}:{{ stepca_group }} {{ stepca_data_dir }}
##################################################################
# 2⃣ Installation de Step-CA et Step-CLI
##################################################################
- name: Télécharger et installer Step-CLI et Step-CA (Ubuntu/Debian)
apt:
deb: "{{ item }}"
with_items:
- "https://dl.smallstep.com/cli/docs-ca-install/latest/step-cli_amd64.deb"
- "https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_amd64.deb"
when: ansible_os_family == "Debian"
- name: Vérifier l'installation de Step-CA et Step-CLI
command: step version
register: step_version
changed_when: false
##################################################################
# 3⃣ Initialisation de Step-CA (si nécessaire)
##################################################################
- name: Vérifier si Step-CA est déjà initialisé
stat:
path: "{{ stepca_data_dir }}/config/ca.json"
register: ca_json
- name: Créer le fichier de mot de passe temporaire pour Step-CA
copy:
dest: "/tmp/stepca_pass"
content: "{{ stepca_init_password }}"
mode: '0644'
- name: Initialiser Step-CA (si nécessaire)
shell: sudo -u {{ stepca_user }} step ca init --name "My Internal CA" \
--dns "{{ stepca_domain }}" \
--address ":{{ stepca_port }}" \
--provisioner "{{ stepca_provisioner }}" \
--password-file /tmp/stepca_pass
args:
creates: "{{ stepca_data_dir }}/config/ca.json"
when: not ca_json.stat.exists
- name: Supprimer le fichier de mot de passe temporaire
file:
path: /tmp/stepca_pass
state: absent
##################################################################
# 4⃣ Création du service systemd
##################################################################
- name: Configurer Step-CA comme un service systemd
copy:
dest: "/etc/systemd/system/{{ stepca_service }}.service"
mode: '0644'
content: |
[Unit]
Description=Step CA Service
After=network.target
[Service]
ExecStart=/usr/bin/step-ca {{ stepca_data_dir }}/config/ca.json
Restart=always
User={{ stepca_user }}
Group={{ stepca_group }}
Environment=STEPPATH={{ stepca_data_dir }}
WorkingDirectory={{ stepca_data_dir }}
[Install]
WantedBy=multi-user.target
- name: Recharger systemd
command: systemctl daemon-reload
- name: Activer et démarrer Step-CA
systemd:
name: "{{ stepca_service }}"
enabled: yes
state: started
##################################################################
# 5⃣ Vérification du service
##################################################################
- name: Vérifier que Step-CA tourne bien
shell: "systemctl is-active {{ stepca_service }}"
register: stepca_status
failed_when: stepca_status.stdout != "active"
changed_when: false
- name: Tester si Step-CA répond sur le port {{ stepca_port }}
uri:
url: "https://localhost:{{ stepca_port }}/acme/acme-directory"
method: GET
status_code: 200
validate_certs: no
register: stepca_test
failed_when: stepca_test.status != 200
changed_when: false

View file

@ -0,0 +1,15 @@
---
- name: Exécuter apt upgrade sur tous les serveurs
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Mettre à jour la liste des paquets APT
apt:
update_cache: yes
- name: Mettre à jour uniquement les paquets installés
apt:
upgrade: yes

View file

@ -0,0 +1,55 @@
---
- name: Ajouter les agents
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Installer GPG si manquant
apt:
name: gnupg
state: present
# 1. Ajouter la clé publique du dépôt Icinga
- name: Ajouter la clé GPG du dépôt Icinga
apt_key:
url: https://packages.icinga.com/icinga.key
state: present
# 2. Ajouter le dépôt Icinga (Debian/Ubuntu)
- name: Ajouter le dépôt Icinga pour Debian/Ubuntu
apt_repository:
repo: "deb https://packages.icinga.com/{{ ansible_distribution | lower }} icinga-{{ ansible_distribution_release | lower }} main"
state: present
# 3. Mettre à jour le cache APT
- name: Mettre à jour le cache APT
apt:
update_cache: yes
# 4. Installer les paquets nécessaires d'Icinga
- name: Installer les paquets Icinga
apt:
name:
- icinga2
- monitoring-plugins
state: latest
- name: Install QEMU Guest Agent
apt:
name: qemu-guest-agent
state: present
- name: Enable and start QEMU Guest Agent
systemd:
name: qemu-guest-agent
enabled: yes
state: started
- name: Verify QEMU Guest Agent is running
shell: systemctl is-active qemu-guest-agent
register: qemu_status
changed_when: false
- name: Display QEMU Guest Agent status
debug:
msg: "QEMU Guest Agent is {{ qemu_status.stdout }}"

View file

@ -0,0 +1,28 @@
rules_input:
- { port: "22", action: "ACCEPT", protocol: "tcp" } # SSH
- { port: "1194", action: "ACCEPT", protocol: "udp" } # RPV-SYMBA
- { port: "1195", action: "ACCEPT", protocol: "udp" } # RPV-SYSADMIN
- { port: "8069", action: "ACCEPT", protocol: "tcp", ctstate: "NEW" }
- { port: "8071", action: "ACCEPT", protocol: "tcp", ctstate: "NEW" }
- { port: "8072", action: "ACCEPT", protocol: "tcp", ctstate: "NEW" }
-
rules_forward:
- { source: "0.0.0.0/0", destination: "10.10.10.10/32", action: "ACCEPT" } # Autoriser le trafic vers les conteneurs
- { source: "0.0.0.0/0", destination: "172.17.0.0/16", action: "ACCEPT" } # Autoriser le trafic vers les conteneurs
- { source: "172.17.0.0/16", destination: "0.0.0.0/0", action: "ACCEPT" } # Autoriser les conteneurs à sortir
rules_output:
- { port: "53", action: "ACCEPT", protocol: "udp" } # Autoriser DNS sortant
- { port: "80", action: "ACCEPT", protocol: "tcp" } # Autoriser HTTP sortant
- { port: "443", action: "ACCEPT", protocol: "tcp" } # Autoriser HTTPS sortant
- { port: "123", action: "ACCEPT", protocol: "udp" } # Autoriser NTP sortant
- { port: "8069", action: "ACCEPT", protocol: "tcp" }
- { port: "8071", action: "ACCEPT", protocol: "tcp" }
- { port: "8072", action: "ACCEPT", protocol: "tcp" }
-
rules_nat_masquerade:
- { source: "172.17.0.0/16", destination: "0.0.0.0/0", action: "MASQUERADE" } # Docker NAT
rules_nat_dnat:
- { proto: "tcp", ext_port: "8080", int_port: "80", int_ip: "172.17.0.2", action: "DNAT" } # Redirection HTTP vers un conteneur
- { proto: "tcp", ext_port: "8443", int_port: "443", int_ip: "172.17.0.3", action: "DNAT" } # Redirection HTTPS vers un autre conteneur

View file

@ -0,0 +1,19 @@
- name: Étendre la partition root avec growpart et resize2fs
hosts: leChoixDuSysadmin
become: yes
tasks:
- name: Installer cloud-utils (nécessaire pour growpart)
apt:
name: cloud-guest-utils
state: present
ignore_errors: yes
- name: Étendre la partition root avec growpart
command: "growpart /dev/sda 1"
ignore_errors: yes
- name: Étendre le système de fichiers ext4
command: "resize2fs /dev/sda1"
ignore_errors: yes

View file

@ -0,0 +1,19 @@
---
- name: Installer les plugins de Chezlepro
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Créer le répertoire /usr/lib/nagios/plugins/chezlepro s'il n'existe pas
ansible.builtin.file:
path: /usr/lib/nagios/plugins/chezlepro
state: directory
mode: "0755"
- name: Synchroniser les plugins personnalisés
ansible.builtin.copy:
src: ./plugins/
dest: /usr/lib/nagios/plugins/chezlepro/
mode: "0755"
owner: root
group: root

View file

@ -0,0 +1,161 @@
---
- name: Setup Docker and deploy containers for Smallstep CA, NGINX+Certbot, and Keycloak
hosts: leChoixDuSysadmin
become: true
tasks:
- name: Install required packages
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- software-properties-common
state: present
update_cache: yes
- name: Add Docker GPG key
command: >
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
- name: Add Docker repository
copy:
content: "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable\n"
dest: /etc/apt/sources.list.d/docker.list
- name: Install Docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
state: latest
update_cache: yes
- name: Start and enable Docker service
systemd:
name: docker
enabled: true
state: started
- name: Install Docker Compose
get_url:
url: "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)"
dest: /usr/local/bin/docker-compose
mode: "0755"
- name: Create directory for Docker Compose setup
file:
path: /opt/docker-setup
state: directory
mode: "0755"
- name: Deploy Docker Compose file
copy:
dest: /opt/docker-setup/docker-compose.yml
content: |
version: '3.8'
services:
step-ca:
image: smallstep/step-ca:latest
container_name: step-ca
ports:
- "9000:9000"
volumes:
- step-ca-data:/home/step
environment:
- STEP_CA_PASSWORD=T1l0u!
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: keycloak
command: start-dev
ports:
- "8080:8080"
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=T1l0u!
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- nginx-config:/etc/nginx/sites-available
- nginx-enabled:/etc/nginx/sites-enabled
- nginx-certbot:/etc/letsencrypt
depends_on:
- step-ca
- keycloak
certbot:
image: certbot/certbot:latest
container_name: certbot
entrypoint: ["/bin/bash", "-c", "trap exit TERM; while :; do sleep 6h & wait; done;"]
volumes:
- nginx-certbot:/etc/letsencrypt
- certbot-config:/var/lib/letsencrypt
volumes:
step-ca-data:
nginx-config:
nginx-enabled:
nginx-certbot:
certbot-config:
- name: Start Docker Compose services
command: docker-compose up -d
args:
chdir: /opt/docker-setup
- name: Configure NGINX for Smallstep CA
copy:
dest: /opt/docker-setup/sites-available/step-ca.conf
content: |
server {
listen 443 ssl;
server_name step-ca.your_domain.com;
ssl_certificate /etc/letsencrypt/live/step-ca.kbr.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/step-ca.kbr.net/privkey.pem;
location / {
proxy_pass http://step-ce.kbr.net:9000/;
}
}
- name: Configure NGINX for Keycloak
copy:
dest: /opt/docker-setup/sites-available/keycloak.conf
content: |
server {
listen 443 ssl;
server_name keycloak.kbr.net;
ssl_certificate /etc/letsencrypt/live/keycloak.kbr.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/keycloak.kbr.net/privkey.pem;
location / {
proxy_pass http://keycloak.kbr.net:8080/;
}
}
- name: Link site configurations
file:
src: /etc/nginx/sites-available/{{ item }}
dest: /etc/nginx/sites-enabled/{{ item }}
state: link
with_items:
- step-ca.conf
- keycloak.conf
- name: Test NGINX configuration
command: nginx -t
- name: Restart NGINX
systemd:
name: nginx
state: restarted

View file

@ -0,0 +1,7 @@
---
- name: Proxmox · Debian 12 cloud-init · template + clones
hosts: proxmox
become: true
gather_facts: false
roles:
- proxmox_deb12_cloudinit

32
playbooks/site.yml Normal file
View file

@ -0,0 +1,32 @@
---
- name: Baseline
hosts: servers
become: true
roles:
- baseline_packages
- baseline_updates
- name: Security
hosts: servers
become: true
roles:
- security_hardening
- firewall_nftables
- name: Monitoring
hosts: servers
become: true
roles:
- monitoring_icinga
- name: DNS authoritative
hosts: dns
become: true
roles:
- dns_powerdns
- name: Reverse proxy
hosts: proxy
become: true
roles:
- reverse_proxy_traefik

View file

@ -0,0 +1,8 @@
baseline_packages_enabled: true
baseline_packages_list:
- ca-certificates
- curl
- vim
- git
- chrony
- qemu-guest-agent

View file

@ -0,0 +1,17 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not baseline_packages_enabled | bool
- name: Install baseline packages
ansible.builtin.apt:
name: "{{ baseline_packages_list }}"
state: present
update_cache: true
- name: Enable qemu-guest-agent (if present)
ansible.builtin.systemd:
name: qemu-guest-agent
enabled: true
state: started
ignore_errors: true

View file

@ -0,0 +1,2 @@
baseline_updates_enabled: true
baseline_updates_upgrade: true

View file

@ -0,0 +1,14 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not baseline_updates_enabled | bool
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Upgrade packages
ansible.builtin.apt:
upgrade: "yes"
when: baseline_updates_upgrade | bool

View file

@ -0,0 +1,24 @@
dns_powerdns_enabled: false
pdns_backend: "sqlite3"
pdns_sqlite_db_path: "/var/lib/powerdns/pdns.sqlite3"
pdns_api_enabled: true
pdns_api_key: "{{ vault_pdns_api_key | default('') }}"
pdns_webserver_address: "127.0.0.1"
pdns_webserver_port: 8081
pdns_zones: []
# Example:
# pdns_zones:
# - name: "chezlepro.ca"
# kind: "Native"
# soa:
# ns: "ns1.chezlepro.ca."
# hostmaster: "hostmaster.chezlepro.ca."
# refresh: 10800
# retry: 3600
# expire: 604800
# minimum: 3600
# nameservers:
# - "ns1.chezlepro.ca."

View file

@ -0,0 +1,5 @@
---
- name: Restart pdns
ansible.builtin.systemd:
name: pdns
state: restarted

View file

@ -0,0 +1,55 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not dns_powerdns_enabled | bool
- name: Install PowerDNS and backend deps
ansible.builtin.apt:
name:
- pdns-server
- sqlite3
state: present
update_cache: true
- name: Ensure db dir exists (sqlite)
ansible.builtin.file:
path: "{{ pdns_sqlite_db_path | dirname }}"
state: directory
mode: "0755"
when: pdns_backend == "sqlite3"
- name: Deploy pdns.conf
ansible.builtin.template:
src: pdns.conf.j2
dest: /etc/powerdns/pdns.conf
mode: "0644"
notify: Restart pdns
- name: Enable and start pdns
ansible.builtin.systemd:
name: pdns
enabled: true
state: started
- name: Create zones + SOA (pdnsutil)
ansible.builtin.command: >
pdnsutil create-zone {{ item.name }}
{% if item.soa is defined %}
{{ item.soa.ns }} {{ item.soa.hostmaster }}
{% endif %}
args:
creates: "/var/lib/powerdns/zones/{{ item.name }}"
loop: "{{ pdns_zones }}"
when: pdns_zones | length > 0
register: pdns_create_zone
changed_when: pdns_create_zone.rc == 0
failed_when: pdns_create_zone.rc not in [0, 1]
- name: Ensure NS records exist
ansible.builtin.command: >
pdnsutil add-record {{ item.0.name }} @ NS {{ item.1 }}
loop: "{{ pdns_zones | subelements('nameservers', skip_missing=True) }}"
register: pdns_add_ns
changed_when: "'Added record' in (pdns_add_ns.stdout | default(''))"
failed_when: pdns_add_ns.rc != 0
when: pdns_zones | length > 0

View file

@ -0,0 +1,16 @@
# Managed by Ansible
launch={{ pdns_backend }}
{% if pdns_backend == "sqlite3" %}
gsqlite3-database={{ pdns_sqlite_db_path }}
{% endif %}
{% if pdns_api_enabled %}
api=yes
api-key={{ pdns_api_key }}
webserver=yes
webserver-address={{ pdns_webserver_address }}
webserver-port={{ pdns_webserver_port }}
{% else %}
api=no
{% endif %}

View file

@ -0,0 +1,14 @@
firewall_nftables_enabled: true
firewall_nftables_flush_ruleset: false
firewall_nftables_allowed_tcp_in:
- { dport: 22, src: "0.0.0.0/0" }
firewall_nftables_allowed_udp_in: []
firewall_nftables_allow_established: true
firewall_nftables_allow_loopback: true
firewall_nftables_policy_input: drop
firewall_nftables_policy_forward: drop
firewall_nftables_policy_output: accept

View file

@ -0,0 +1,6 @@
---
- name: Reload nftables
ansible.builtin.systemd:
name: nftables
state: reloaded
ignore_errors: true

View file

@ -0,0 +1,23 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not firewall_nftables_enabled | bool
- name: Install nftables
ansible.builtin.apt:
name: nftables
state: present
update_cache: true
- name: Deploy nftables config
ansible.builtin.template:
src: nftables.conf.j2
dest: /etc/nftables.conf
mode: "0644"
notify: Reload nftables
- name: Enable and start nftables
ansible.builtin.systemd:
name: nftables
enabled: true
state: started

View file

@ -0,0 +1,42 @@
#!/usr/sbin/nft -f
{% if firewall_nftables_flush_ruleset %}
flush ruleset
{% endif %}
table inet filter {
chain input {
type filter hook input priority 0;
policy {{ firewall_nftables_policy_input }};
{% if firewall_nftables_allow_loopback %}
iif "lo" accept
{% endif %}
{% if firewall_nftables_allow_established %}
ct state established,related accept
{% endif %}
# ICMP / ICMPv6 minimal
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
{% for r in firewall_nftables_allowed_tcp_in %}
ip saddr {{ r.src }} tcp dport {{ r.dport }} accept
{% endfor %}
{% for r in firewall_nftables_allowed_udp_in %}
ip saddr {{ r.src }} udp dport {{ r.dport }} accept
{% endfor %}
}
chain forward {
type filter hook forward priority 0;
policy {{ firewall_nftables_policy_forward }};
}
chain output {
type filter hook output priority 0;
policy {{ firewall_nftables_policy_output }};
}
}

View file

@ -0,0 +1,14 @@
monitoring_icinga_enabled: true
monitoring_icinga_agent_enabled: true
monitoring_icinga_plugins_enabled: false
monitoring_icinga_director_enabled: false
monitoring_icinga_agent_pkg: "icinga2"
monitoring_icinga_agent_service: "icinga2"
monitoring_icinga_plugins_dest_dir: "/usr/lib/nagios/plugins"
monitoring_icinga_director_api_url: ""
monitoring_icinga_director_api_token: ""
monitoring_icinga_director_validate_certs: false
monitoring_icinga_director_objects: []

View file

@ -0,0 +1,6 @@
---
- name: Restart Icinga2
ansible.builtin.systemd:
name: icinga2
state: restarted
ignore_errors: true

View file

@ -0,0 +1,12 @@
---
- name: Install Icinga2
ansible.builtin.apt:
name: "{{ monitoring_icinga_agent_pkg }}"
state: present
update_cache: true
- name: Enable and start Icinga2
ansible.builtin.systemd:
name: "{{ monitoring_icinga_agent_service }}"
enabled: true
state: started

View file

@ -0,0 +1,20 @@
---
- name: Validate Director settings
ansible.builtin.assert:
that:
- monitoring_icinga_director_api_url | length > 0
- monitoring_icinga_director_api_token | length > 0
fail_msg: "Set monitoring_icinga_director_api_url/token when monitoring_icinga_director_enabled=true"
- name: Push Director objects
ansible.builtin.uri:
url: "{{ monitoring_icinga_director_api_url }}"
method: POST
headers:
Accept: "application/json"
Authorization: "Bearer {{ monitoring_icinga_director_api_token }}"
body_format: json
validate_certs: "{{ monitoring_icinga_director_validate_certs | bool }}"
body: "{{ item }}"
loop: "{{ monitoring_icinga_director_objects }}"
no_log: true

View file

@ -0,0 +1,19 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not monitoring_icinga_enabled | bool
- name: Agent
ansible.builtin.import_tasks: agent.yml
when: monitoring_icinga_agent_enabled | bool
tags: [monitoring, icinga, agent]
- name: Plugins
ansible.builtin.import_tasks: plugins.yml
when: monitoring_icinga_plugins_enabled | bool
tags: [monitoring, icinga, plugins]
- name: Director
ansible.builtin.import_tasks: director.yml
when: monitoring_icinga_director_enabled | bool
tags: [monitoring, icinga, director]

View file

@ -0,0 +1,13 @@
---
- name: Ensure plugins dir
ansible.builtin.file:
path: "{{ monitoring_icinga_plugins_dest_dir }}"
state: directory
mode: "0755"
- name: Deploy Chezlepro plugins (if provided)
ansible.builtin.copy:
src: "plugins/"
dest: "{{ monitoring_icinga_plugins_dest_dir }}/"
mode: "0755"
notify: Restart Icinga2

View file

@ -0,0 +1,26 @@
# Proxmox (adapter à votre environnement)
pve_node: "asgard"
pve_storage: "CephNVMe"
pve_cloudinit_storage: "CephNVMe"
pve_snippets_storage: "cephFS"
# Template Debian 12
deb12_template_id: 1681501
deb12_template_name: "debian12-genericcloud"
deb12_cloud_image_url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
deb12_template_bridge: "vmbr1"
deb12_template_machine: "q35"
deb12_template_cpu: "host"
# Cloud-init user-data
ci_user: "ansible"
ci_lock_passwd: true
ci_ssh_pwauth: false
ci_password_hash: "" # idéalement via vault
ci_authorized_keys: [] # liste de clés publiques
ci_nameserver_default: "1.1.1.1"
ci_searchdomain_default: ""
apt_proxy_url: ""
# VMs à cloner
vms: []

View file

@ -0,0 +1,26 @@
---
# Active 'snippets' sur le storage ciblé et dépose le cloud-config
- name: Activer le type 'snippets' sur {{ pve_snippets_storage }}
command: >
pvesm set {{ pve_snippets_storage }}
--content snippets,iso,vztmpl,backup
changed_when: false
- name: Déterminer le dossier snippets selon le storage
set_fact:
snippets_dir: >-
{{ '/var/lib/vz/snippets'
if pve_snippets_storage == 'local'
else '/mnt/pve/' + pve_snippets_storage + '/snippets' }}
- name: Créer le dossier snippets
file:
path: "{{ snippets_dir }}"
state: directory
mode: "0755"
- name: Déposer le #cloud-config Chezlepro
template:
src: "chezlepro-user-data.yml.j2"
dest: "{{ snippets_dir }}/chezlepro-user-data.yml"
mode: "0644"

View file

@ -0,0 +1,87 @@
---
- name: Préparer le dossier temporaire dimages
file:
path: "/var/lib/vz/template/tmp"
state: directory
mode: "0755"
- name: Chemin local de l'image cloud
set_fact:
deb12_img_path: "/var/lib/vz/template/tmp/debian-12-genericcloud-amd64.qcow2"
- name: Vérifier si l'image est déjà présente
stat:
path: "{{ deb12_img_path }}"
register: deb12_img
- name: Télécharger l'image cloud Debian 12 (si absente)
get_url:
url: "{{ deb12_cloud_image_url }}"
dest: "{{ deb12_img_path }}"
mode: "0644"
when: not deb12_img.stat.exists
# Defaults "safe"
- name: Définir les valeurs par défaut du template (si non définies)
set_fact:
deb12_template_bridge: "{{ deb12_template_bridge | default('vmbr1') }}"
# deb12_template_machine: "{{ deb12_template_machine | default('q35') }}"
deb12_template_cpu: "{{ deb12_template_cpu | default('host') }}"
- name: Vérifier si le template existe déjà
command: "qm status {{ deb12_template_id }}"
register: tmpl_status
changed_when: false
failed_when: false
- name: Créer le template cloud-init (si absent)
shell: |
set -euo pipefail
# 1) Créer la VM si absente (en imposant q35 + cpu host dès le départ)
qm create {{ deb12_template_id }} \
--name {{ deb12_template_name }} \
--memory 1024 --cores 2 \
--cpu {{ deb12_template_cpu }} \
--ostype l26 \
--net0 virtio,bridge={{ deb12_template_bridge }} \
--agent enabled=1
# 2) Disque OS (qcow2 importé)
qm set {{ deb12_template_id }} --scsihw virtio-scsi-pci
qm importdisk {{ deb12_template_id }} {{ deb12_img_path }} {{ pve_storage }} --format qcow2
qm set {{ deb12_template_id }} --scsi0 {{ pve_storage }}:vm-{{ deb12_template_id }}-disk-0
# 3) Cloud-init
qm set {{ deb12_template_id }} --ide2 {{ pve_cloudinit_storage }}:cloudinit
qm set {{ deb12_template_id }} --boot c --bootdisk scsi0
# 5) Convertir en template
qm template {{ deb12_template_id }}
args:
executable: /bin/bash
when: tmpl_status.rc != 0
# IMPORTANT : pour un template déjà existant, on force machine/cpu, MAIS on ne retouche PAS net0 (évite tout changement de MAC).
- name: Assurer machine q35 sur template existant
command: "qm set {{ deb12_template_id }} --machine {{ deb12_template_machine }}"
when: tmpl_status.rc == 0
changed_when: true
- name: Assurer CPU host sur template existant
command: "qm set {{ deb12_template_id }} --cpu {{ deb12_template_cpu }}"
when: tmpl_status.rc == 0
changed_when: true
- name: Assurer agent
command: "qm set {{ deb12_template_id }} --agent enabled=1"
when: tmpl_status.rc == 0
changed_when: true
- name: Afficher le template
command: "qm config {{ deb12_template_id }}"
register: tmplcfg
changed_when: false
- debug:
var: tmplcfg.stdout_lines

View file

@ -0,0 +1,46 @@
---
- name: Préparer le dossier temporaire dimages
file:
path: "/var/lib/vz/template/tmp"
state: directory
mode: "0755"
- name: Chemin local de l'image cloud
set_fact:
deb12_img_path: "/var/lib/vz/template/tmp/debian-12-genericcloud-amd64.qcow2"
- name: Vérifier si l'image est déjà présente
stat:
path: "{{ deb12_img_path }}"
register: deb12_img
- name: Télécharger l'image cloud Debian 12 (si absente)
get_url:
url: "{{ deb12_cloud_image_url }}"
dest: "{{ deb12_img_path }}"
mode: "0644"
when: not deb12_img.stat.exists
- name: Créer le template cloud-init (si absent)
shell: |
set -e
if ! qm status {{ deb12_template_id }} >/dev/null 2>&1; then
qm create {{ deb12_template_id }} --name {{ deb12_template_name }} --memory 1024 --cores 2 \
--net0 virtio,bridge=vmbr0 --agent enabled=1
qm set {{ deb12_template_id }} --scsihw virtio-scsi-pci
qm importdisk {{ deb12_template_id }} {{ deb12_img_path }} {{ pve_storage }} --format qcow2
qm set {{ deb12_template_id }} --scsi0 {{ pve_storage }}:vm-{{ deb12_template_id }}-disk-0
qm set {{ deb12_template_id }} --ide2 {{ pve_cloudinit_storage }}:cloudinit
qm set {{ deb12_template_id }} --boot c --bootdisk scsi0
qm template {{ deb12_template_id }}
fi
args:
executable: /bin/bash
- name: Afficher le template créé
shell: "qm config {{ deb12_template_id }}"
register: tmplcfg
changed_when: false
- debug:
var: tmplcfg.stdout_lines

View file

@ -0,0 +1,10 @@
---
- name: Provisionner les VMs depuis le template
include_tasks: 21_clone_one.yml
loop: "{{ vms }}"
loop_control:
loop_var: vm
label: "{{ vm.name }}"
vars:
ci_nameserver: "{{ nameserver | default('1.1.1.1') }}"
ci_searchdomain: "{{ searchdomain | default('') }}"

View file

@ -0,0 +1,101 @@
# ============================================================================
# Création/Configuration VM depuis template Debian 12 genericcloud
# Objectif: q35 + MAC stable + ipconfig0 stable (Ansible-only)
# ============================================================================
- name: Cloner la VM (si absente)
shell: |
set -euo pipefail
if ! qm status {{ vm.id }} >/dev/null 2>&1; then
qm clone {{ deb12_template_id }} {{ vm.id }} --name {{ vm.name }} --full 1 --target {{ pve_node }}
fi
args: { executable: /bin/bash }
# --- Lecture config actuelle -------------------------------------------------
- name: Lire la config Proxmox de la VM
command: "qm config {{ vm.id }}"
register: vm_cfg
changed_when: false
- name: Lire le statut de la VM
command: "qm status {{ vm.id }}"
register: vm_status
changed_when: false
# --- Appliquer hardware VM (host + cores/mem) -----------
- name: Appliquer CPU host + cores/memory stable
command: >
qm set {{ vm.id }}
--cpu host
--cores {{ vm.cores }}
--memory {{ vm.memory }}
changed_when: true
# --- Disque -----------------------------------------------------------------
- name: Redimensionner le disque système (idempotence simple)
command: "qm resize {{ vm.id }} scsi0 {{ vm.disk_gb }}G"
register: resize_out
failed_when: >
resize_out.rc != 0 and
('no need to resize' not in (resize_out.stderr | lower)) and
('shrinking' not in (resize_out.stderr | lower))
changed_when: resize_out.rc == 0
# --- Cloud-init : sshkeys + réseau + DNS + hostname via meta-data -----------
- name: Créer le dossier pour les clés (si absent)
file:
path: /root/.cache/ansible
state: directory
owner: root
group: root
mode: "0700"
- name: Déposer la clé SSH publique de {{ vm.name }}
copy:
content: "{{ vm.sshkeys }}"
dest: "/root/.cache/ansible/qm-sshkeys-{{ vm.id }}.pub"
mode: "0600"
- name: Paramétrer cloud-init (user, sshkeys, réseau, DNS)
command: >
qm set {{ vm.id }}
--ciuser {{ vm.ciuser | default('ansible') }}
--sshkeys /root/.cache/ansible/qm-sshkeys-{{ vm.id }}.pub
--ipconfig0 {{ vm.ipconfig0 }}
--nameserver {{ ci_nameserver }}{{ (' --searchdomain ' + ci_searchdomain) if ci_searchdomain else '' }}
--ciupgrade 1
changed_when: true
# --- Hostname via meta-data NoCloud ----------------------------------------
- name: Générer le meta-data (hostname) pour {{ vm.name }}
copy:
dest: "{{ snippets_dir }}/meta-{{ vm.id }}.yml"
mode: "0644"
content: |
instance-id: {{ vm.id }}
local-hostname: {{ vm.name }}
- name: Associer user-data + meta-data via cicustom
command: >
qm set {{ vm.id }}
--cicustom "user={{ pve_snippets_storage }}:snippets/chezlepro-user-data.yml,meta={{ pve_snippets_storage }}:snippets/meta-{{ vm.id }}.yml"
changed_when: true
- name: Régénérer le disque cloud-init
command: "qm cloudinit update {{ vm.id }}"
changed_when: true
# --- Démarrage --------------------------------------------------------------
- name: Start VM (tolerate already running)
command: "qm start {{ vm.id }}"
register: vm_start
failed_when: >
vm_start.rc != 0 and
('already running' not in (vm_start.stderr | lower))
changed_when: vm_start.rc == 0

View file

@ -0,0 +1,76 @@
---
- name: Cloner la VM (si absente)
shell: |
set -e
if ! qm status {{ vm.id }} >/dev/null 2>&1; then
qm clone {{ deb12_template_id }} {{ vm.id }} --name {{ vm.name }} --full 1 --target {{ pve_node }}
fi
args: { executable: /bin/bash }
- name: Configurer CPU/RAM/NIC (+ VLAN tag si défini)
shell: |
set -e
qm set {{ vm.id }} --cores {{ vm.cores }} --memory {{ vm.memory }} \
--net0 virtio,bridge={{ vm.bridge }}{{ (',tag=' + vm.tag|string) if (vm.tag is defined) else '' }}
args: { executable: /bin/bash }
- name: Redimensionner le disque système
shell: "qm resize {{ vm.id }} scsi0 {{ vm.disk_gb }}G"
args: { executable: /bin/bash }
- name: Créer le dossier pour les clés (si absent)
file:
path: /root/.cache/ansible
state: directory
owner: root
group: root
mode: "0700"
- name: Déposer la clé SSH publique de {{ vm.name }}
copy:
content: "{{ vm.sshkeys }}"
dest: "/root/.cache/ansible/qm-sshkeys-{{ vm.id }}.pub"
mode: "0600"
- name: Paramétrer cloud-init (user, ssh, réseau, DNS)
shell: |
set -e
qm set {{ vm.id }} --ipconfig0 "{{ vm.ipconfig0 }}"
qm set {{ vm.id }} --nameserver {{ ci_nameserver }}{{ (' --searchdomain ' + ci_searchdomain) if ci_searchdomain else '' }}
qm set {{ vm.id }} --ciupgrade 1
args: { executable: /bin/bash }
# --- Nouveau : hostname via meta-data NoCloud -------------------------------
- name: Générer le meta-data (hostname) pour {{ vm.name }}
copy:
dest: "{{ snippets_dir }}/meta-{{ vm.id }}.yml"
mode: "0644"
content: |
instance-id: {{ vm.id }}
local-hostname: {{ vm.name }}
- name: Associer user-data + meta-data via cicustom
shell: |
set -e
qm set {{ vm.id }} --cicustom "user={{ pve_snippets_storage }}:snippets/chezlepro-user-data.yml,meta={{ pve_snippets_storage }}:snippets/meta-{{ vm.id }}.yml"
args: { executable: /bin/bash }
# ---------------------------------------------------------------------------
- name: Régénérer le disque cloud-init
shell: "qm cloudinit update {{ vm.id }}"
args: { executable: /bin/bash }
- name: Start VM (tolerate already running)
ansible.builtin.command: qm start {{ vm.id }}
register: vm_start
failed_when: >
vm_start.rc != 0 and
('already running' not in vm_start.stderr | lower)
changed_when: >
vm_start.rc == 0
#- name: Démarrer la VM
# shell: "qm start {{ vm.id }}"
# args: { executable: /bin/bash }

View file

@ -0,0 +1,4 @@
---
- import_tasks: 00_snippets.yml
- import_tasks: 10_template.yml
- import_tasks: 20_clones.yml

View file

@ -0,0 +1,23 @@
#cloud-config
users:
- name: {{ ci_user }}
sudo: "ALL=(ALL) NOPASSWD:ALL"
lock_passwd: {{ 'true' if ci_lock_passwd else 'false' }}
ssh_pwauth: {{ 'true' if ci_ssh_pwauth else 'false' }}
{% if ci_password_hash|length > 0 %}
passwd: "{{ ci_password_hash }}"
{% endif %}
ssh_authorized_keys:
{% for k in ci_authorized_keys %}
- {{ k }}
{% endfor %}
package_update: true
packages:
- qemu-guest-agent
- chrony
runcmd:
- systemctl enable --now qemu-guest-agent
- systemctl enable --now chrony
- apt-get -y autoremove

View file

@ -0,0 +1,22 @@
reverse_proxy_traefik_enabled: false
traefik_user: "traefik"
traefik_group: "traefik"
traefik_version: "v3.0.0"
traefik_arch: "linux_amd64"
traefik_download_base: "https://github.com/traefik/traefik/releases/download"
traefik_bin_path: "/usr/local/bin/traefik"
traefik_config_dir: "/etc/traefik"
traefik_data_dir: "/var/lib/traefik"
traefik_listen_http: 80
traefik_listen_https: 443
traefik_dashboard_enabled: false
traefik_log_level: "INFO"
traefik_acme_enabled: false
traefik_acme_email: ""
traefik_acme_storage: "/var/lib/traefik/acme.json"

View file

@ -0,0 +1,5 @@
---
- name: Restart traefik
ansible.builtin.systemd:
name: traefik
state: restarted

View file

@ -0,0 +1,73 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not reverse_proxy_traefik_enabled | bool
- name: Create traefik group
ansible.builtin.group:
name: "{{ traefik_group }}"
system: true
- name: Create traefik user
ansible.builtin.user:
name: "{{ traefik_user }}"
group: "{{ traefik_group }}"
system: true
create_home: false
shell: /usr/sbin/nologin
- name: Create directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ traefik_user }}"
group: "{{ traefik_group }}"
mode: "0755"
loop:
- "{{ traefik_config_dir }}"
- "{{ traefik_config_dir }}/dynamic"
- "{{ traefik_data_dir }}"
- name: Download traefik
ansible.builtin.get_url:
url: "{{ traefik_download_base }}/{{ traefik_version }}/traefik_{{ traefik_version }}_{{ traefik_arch }}.tar.gz"
dest: "/tmp/traefik_{{ traefik_version }}.tar.gz"
mode: "0644"
- name: Extract traefik binary
ansible.builtin.unarchive:
src: "/tmp/traefik_{{ traefik_version }}.tar.gz"
dest: "/tmp/traefik_{{ traefik_version }}"
remote_src: true
creates: "/tmp/traefik_{{ traefik_version }}/traefik"
- name: Install traefik binary
ansible.builtin.copy:
src: "/tmp/traefik_{{ traefik_version }}/traefik"
dest: "{{ traefik_bin_path }}"
mode: "0755"
remote_src: true
notify: Restart traefik
- name: Deploy traefik static config
ansible.builtin.template:
src: traefik.yml.j2
dest: "{{ traefik_config_dir }}/traefik.yml"
owner: "{{ traefik_user }}"
group: "{{ traefik_group }}"
mode: "0644"
notify: Restart traefik
- name: Deploy systemd service
ansible.builtin.template:
src: traefik.service.j2
dest: /etc/systemd/system/traefik.service
mode: "0644"
notify: Restart traefik
- name: Enable and start traefik
ansible.builtin.systemd:
name: traefik
daemon_reload: true
enabled: true
state: started

View file

@ -0,0 +1,17 @@
[Unit]
Description=Traefik
After=network-online.target
Wants=network-online.target
[Service]
User={{ traefik_user }}
Group={{ traefik_group }}
ExecStart={{ traefik_bin_path }} --configFile={{ traefik_config_dir }}/traefik.yml
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,27 @@
log:
level: {{ traefik_log_level }}
entryPoints:
web:
address: ":{{ traefik_listen_http }}"
websecure:
address: ":{{ traefik_listen_https }}"
api:
dashboard: {{ 'true' if traefik_dashboard_enabled else 'false' }}
insecure: false
providers:
file:
directory: "{{ traefik_config_dir }}/dynamic"
watch: true
{% if traefik_acme_enabled %}
certificatesResolvers:
le:
acme:
email: "{{ traefik_acme_email }}"
storage: "{{ traefik_acme_storage }}"
httpChallenge:
entryPoint: web
{% endif %}

View file

@ -0,0 +1,18 @@
security_hardening_enabled: true
security_sysctl:
net.ipv4.ip_forward: 0
net.ipv4.conf.all.accept_redirects: 0
net.ipv4.conf.default.accept_redirects: 0
net.ipv4.conf.all.send_redirects: 0
net.ipv4.conf.default.send_redirects: 0
net.ipv4.conf.all.rp_filter: 1
net.ipv4.conf.default.rp_filter: 1
security_ssh_hardening_enabled: true
security_sshd_settings:
PasswordAuthentication: "no"
PermitRootLogin: "no"
ChallengeResponseAuthentication: "no"
UsePAM: "yes"
X11Forwarding: "no"

View file

@ -0,0 +1,6 @@
---
- name: Reload ssh
ansible.builtin.systemd:
name: ssh
state: reloaded
ignore_errors: true

View file

@ -0,0 +1,24 @@
---
- name: Gate
ansible.builtin.meta: end_host
when: not security_hardening_enabled | bool
- name: Apply sysctl hardening
ansible.posix.sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
sysctl_set: true
reload: true
loop: "{{ security_sysctl | dict2items }}"
- name: SSH hardening (drop-in)
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/99-chezlepro-hardening.conf
mode: "0644"
content: |
{% for k,v in security_sshd_settings.items() %}
{{ k }} {{ v }}
{% endfor %}
when: security_ssh_hardening_enabled | bool
notify: Reload ssh