This commit is contained in:
Daniel Allaire 2026-02-08 18:21:55 -05:00
parent c59c5e9ef1
commit 0a70d10bab
9 changed files with 319 additions and 35 deletions

38
CHECKLIST_REBUILD.md Normal file
View file

@ -0,0 +1,38 @@
# Checklist rebuild — VPS BBB (1 page)
## Pré-vol (avant Ansible)
- [ ] DNS : `A` de `bbb.chezlepro.ca` pointe vers lIPv4 du VPS
- [ ] (Optionnel) DNS : `AAAA` si IPv6 utilisée
- [ ] Accès console / 2 sessions SSH ouvertes (anti-lockout)
- [ ] Clé SSH du user `ansible` installée
- [ ] Sudo fonctionnel (au moins une fois via `--ask-become-pass`)
## Déploiement
- [ ] `make check`
- [ ] 1ère passe si nécessaire : `ansible-playbook -i inventory.ini site.yml --become --ask-become-pass`
- [ ] Déploiement standard : `make run`
## Contrôles post-déploiement (OS)
- [ ] Hostname : `hostnamectl` contient `bbb.chezlepro.ca`
- [ ] Locale : `locale``LANG=en_US.UTF-8`
- [ ] Swap actif : `swapon --show`
## Contrôles post-déploiement (services)
- [ ] `systemctl is-active docker`
- [ ] `systemctl is-active fail2ban`
- [ ] `systemctl is-active hostfilter-nft`
## Contrôles post-déploiement (firewall)
- [ ] Syntaxe : `sudo nft -c -f /etc/nftables/hostfilter.nft`
- [ ] Règles actives : `sudo nft list ruleset | sed -n '1,220p'`
- [ ] SSH restreint aux CIDR attendus (IPv4), IPv6 fermé si non utilisé
- [ ] Ports ouverts (attendus) :
- SSH : 22/TCP
- BBB : 80/TCP, 443/TCP, 1638432768/UDP
- TURN : 3478/TCP+UDP, 5349/TCP, relais UDP (ex: 5000055000/UDP)
## Smoke test (script)
- [ ] `sudo ./smoke.sh` retourne tout “OK” et naffiche pas derreurs
## Prochaine étape
- [ ] Installer BBB + coturn (rôle séparé) et refaire une checklist “WebRTC/BBB”.

View file

@ -1,9 +1,9 @@
SHELL := /bin/bash
INVENTORY ?= inventory.ini
PLAYBOOK ?= site.yml
PLAYBOOK ?= site.yml
EXTRA_VARS ?=
ANSIBLE ?= ansible-playbook
ANSIBLE ?= ansible-playbook
.PHONY: help ping check run dry-run diff vars tree clean
@ -41,17 +41,7 @@ vars:
ansible -i $(INVENTORY) $(host) -m debug -a "var=hostvars[inventory_hostname]" | sed -n '1,200p'
tree:
@python3 - <<'PY'
import os
for root, dirs, files in os.walk('.'):
dirs[:] = [d for d in dirs if d not in {'.git','__pycache__','.venv','.cache'}]
level = root.count(os.sep)
indent = ' ' * level
print(f"{indent}{os.path.basename(root)}/")
subindent = ' ' * (level + 1)
for f in sorted(files):
print(f"{subindent}{f}")
PY
@python3 -c $'import os\nfor root, dirs, files in os.walk("."):\n dirs[:] = [d for d in dirs if d not in {".git","__pycache__",".venv",".cache"}]\n level = root.count(os.sep)\n indent = " " * level\n print(f"{indent}{os.path.basename(root)}/")\n subindent = " " * (level + 1)\n for f in sorted(files):\n print(f"{subindent}{f}")'
clean:
@find . -name "*.retry" -delete

View file

@ -0,0 +1,198 @@
# Runbook de référence — VPS BigBlueButton (BBB) — Préparation & Recréation
Ce document sert de **procédure de reconstitution** (“rebuild/runbook”) pour remettre en place un VPS prêt à héberger BigBlueButton (BBB) avec :
- Ubuntu **22.04 LTS**
- **Docker** (requis par BBB 3.x)
- Firewall **nftables** (table dédiée `inet hostfilter`, compatible avec les règles Docker)
- **Fail2ban** (action nftables)
- Durcissement SSH (clé seulement, root interdit)
- journald persistant + rotation
- sysctl (incl. forwarding pour Docker)
- swap idempotent
---
## 1) Hypothèses & invariants
- VPS public (OVH Beauharnois) avec IPv4 publique.
- FQDN prévu : `bbb.chezlepro.ca`
- Distribution : **Ubuntu 22.04**
- Linstall BBB viendra ensuite (BBB 3.x).
---
## 2) Paramètres à personnaliser (avant exécution)
### FQDN
- `fqdn: "bbb.chezlepro.ca"`
### Compte dexploitation Ansible
- `admin_user: "ansible"`
- `admin_pubkeys: [...]` (clé(s) SSH ED25519)
### SSH allowlist (IPv4)
Range **69.70.26.50 → 69.70.26.62** :
```yaml
ssh_allow_cidrs_v4:
- "69.70.26.50/31" # .50-.51
- "69.70.26.52/30" # .52-.55
- "69.70.26.56/30" # .56-.59
- "69.70.26.60/31" # .60-.61
- "69.70.26.62/32" # .62
```
### SSH allowlist (IPv6)
Si vous navez pas un /128 fixe, **désactiver SSH en IPv6** (temps de chantier) :
```yaml
ssh_allow_cidrs_v6: ["::1/128"]
```
### Ports BBB + TURN
- BBB : `80/tcp`, `443/tcp`, `1638432768/udp`
- TURN : `3478/tcp+udp`, `5349/tcp`, relais UDP (plage à réduire)
Recommandation : réduire le relais TURN à **5000055000/udp** :
```yaml
turn_relay_udp_min: 50000
turn_relay_udp_max: 55000
```
> À répliquer ensuite dans coturn : `min-port=50000`, `max-port=55000`.
---
## 3) Checklist DNS / réseau (avant dinstaller BBB)
1. Le DNS `A` de `bbb.chezlepro.ca` pointe vers lIPv4 du VPS.
2. (Optionnel) `AAAA` si IPv6 utilisée.
3. Les ports nécessaires sont ouverts côté firewall VPS (nftables).
---
## 4) Dépôt / arborescence attendue
- `inventory.ini` (définit lhôte `bbb`)
- `group_vars/all.yml` (variables)
- `site.yml` (playbook)
- `roles/bbb_vps_prep/*` (rôle de préparation)
Idéalement : initialiser un dépôt git pour historiser les changements :
```bash
git init
git add .
git commit -m "Initial: BBB VPS prep (Ubuntu 22.04, nftables, docker)"
```
---
## 5) Procédure dexécution (rebuild)
### 5.1 Amorçage sudo (première exécution)
Si le user `ansible` na pas encore NOPASSWD :
```bash
ansible-playbook -i inventory.ini site.yml --become --ask-become-pass
```
Après cette première passe, `/etc/sudoers.d/90-ansible` est créé et les runs suivants se font sans prompt.
### 5.2 Exécution standard
```bash
make run
```
### 5.3 Dry-run
⚠️ Le `--check` ne crée pas réellement les services/units; certaines tâches doivent être conditionnées par `not ansible_check_mode`.
```bash
make dry-run
```
---
## 6) Contrôles post-déploiement
### 6.1 Services
```bash
systemctl is-active docker && echo "docker OK"
systemctl is-active fail2ban && echo "fail2ban OK"
systemctl is-active hostfilter-nft && echo "hostfilter OK"
```
### 6.2 Firewall
```bash
sudo nft list ruleset | sed -n '1,220p'
sudo nft -c -f /etc/nftables/hostfilter.nft
```
### 6.3 SSH
Valider que root est interdit et que lauth est clé-only :
```bash
sudo sshd -t
sudo systemctl reload ssh
```
---
## 7) Points de conception importants (à ne pas casser)
### 7.1 nftables + Docker
- Docker gère ses propres tables (`ip nat`, `ip filter`, etc.).
- Notre politique “host” est dans `table inet hostfilter`.
- Le script **ne flush pas** le ruleset complet : il supprime/recrée **uniquement** `inet hostfilter`.
### 7.2 Fail2ban + nftables
Fail2ban ajoute sa table `inet f2b-table` et hook avant `hostfilter`, ce qui est souhaité.
### 7.3 Swap idempotent
- Ne pas relancer `mkswap` si le swap est actif.
- Logique : `blkid TYPE` + `swapon --show`.
---
## 8) Dépannage rapide (symptômes connus)
### “Missing sudo password”
- Lancer une fois avec `--ask-become-pass`, ou créer `NOPASSWD` manuellement dans `/etc/sudoers.d/`.
### “Destination directory /etc/nftables does not exist”
- Créer le répertoire avant de templater le fichier :
`file: path=/etc/nftables state=directory`
### hostfilter-nft.service échoue / erreur nft
- Lire :
```bash
journalctl -xeu hostfilter-nft --no-pager -l | tail -200
sudo nft -c -f /etc/nftables/hostfilter.nft
```
- Éviter certains patterns dans sets (ex: `iifname { "docker0", "br-*" }`) et préférer des règles séparées.
### Warnings Ansible Python interpreter
- Pinner linterpréteur dans `inventory.ini` :
```ini
bbb ansible_python_interpreter=/usr/bin/python3.10
```
---
## 9) Sécurité — recommandations “après chantier”
- Restreindre SSH à des CIDR stricts (fait).
- Option : limiter le trafic depuis les interfaces Docker vers le host (au lieu de `accept` global).
- Réduire la plage UDP TURN relay (fait/recommandé).
- Ajouter une sauvegarde régulière des configs (git + backup `/etc` ciblé).
- Ajouter une supervision (ex: node_exporter + Loki/Promtail si souhaité).
---
## 10) Prochaine étape (installation BBB + coturn)
Créer un rôle `bbb_install` séparé qui :
1) installe BBB 3.x (docker)
2) installe et configure coturn
3) configure Lets Encrypt pour `bbb.chezlepro.ca`
4) applique la plage UDP relay TURN **alignée** avec nftables
5) exécute des tests de santé (ports + WebRTC)
---
### Historique
- 2026-02-08 : Base VPS opérationnelle (SSH, nftables, fail2ban, docker, journald, sysctl, swap idempotent).

View file

@ -4,12 +4,19 @@ fqdn: "bbb.chezlepro.ca"
# --- Admin user + keys ---
admin_user: "ansible"
admin_pubkeys:
- "ssh-ed25519 AAAA...REMPLACE... ton_key_ed25519 ..."
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILKb7XpYYfKuoBIVprPYX1VVdebRMvpiV2NcsZYaYzbP Cle ansible pour le royaume chezlepro"
# --- SSH hardening ---
ssh_port: 22
ssh_allow_cidrs_v4: ["0.0.0.0/0"] # RECO: remplace par ton IP fixe (ex: "1.2.3.4/32")
ssh_allow_cidrs_v6: ["::/0"] # RECO: remplace par ton IP /128 si applicable
ssh_allow_cidrs_v4:
- "69.70.26.50/31" # .50-.51
- "69.70.26.52/30" # .52-.55
- "69.70.26.56/30" # .56-.59
- "69.70.26.60/31" # .60-.61
- "69.70.26.62/32" # .62
ssh_allow_cidrs_v6: ["::1/128"]
ssh_allow_users: ["ansible"]
disable_password_auth: true
@ -23,8 +30,8 @@ bbb_udp_max: 32768
turn_enabled: true
turn_listen_port: 3478 # TCP/UDP
turn_tls_port: 5349 # TURN over TLS (standard). 443 est conflictuel sur 1 IP avec nginx/BBB
turn_relay_udp_min: 32769
turn_relay_udp_max: 65535
turn_relay_udp_min: 50000
turn_relay_udp_max: 55000
# --- Hardening toggles ---
enable_unattended_upgrades: true

View file

@ -1,2 +1,4 @@
[vps]
bbb ansible_host=YOUR.VPS.IP ansible_user=ansible
bbb ansible_host=158.69.198.149 ansible_user=ansible ansible_ssh_extra_args='-o PreferredAuthentications=publickey' ansible_scp_if_ssh=True
bbb ansible_python_interpreter=/usr/bin/python3

View file

@ -3,29 +3,35 @@
ansible.builtin.service:
name: ssh
state: reloaded
when: not ansible_check_mode
- name: restart fail2ban
ansible.builtin.service:
name: fail2ban
state: restarted
when: not ansible_check_mode
- name: restart journald
ansible.builtin.service:
name: systemd-journald
state: restarted
when: not ansible_check_mode
- name: reload sysctl
ansible.builtin.command: sysctl --system
changed_when: false
when: not ansible_check_mode
- name: restart docker
ansible.builtin.systemd:
name: docker
state: restarted
daemon_reload: true
when: not ansible_check_mode
- name: restart hostfilter firewall
ansible.builtin.systemd:
name: hostfilter-nft
state: restarted
daemon_reload: true
when: not ansible_check_mode

View file

@ -85,6 +85,7 @@
- name: Validate sshd configuration
ansible.builtin.command: sshd -t
check_mode: no
changed_when: false
# --- journald persistence ---
@ -151,7 +152,7 @@
name: fail2ban
enabled: true
state: started
when: enable_fail2ban | bool
when: enable_fail2ban | bool and not ansible_check_mode
# --- Docker CE (repo officiel) ---
- name: Map architecture for Docker repo
@ -205,7 +206,7 @@
name: docker
enabled: true
state: started
when: docker_install | bool
when: docker_install | bool and not ansible_check_mode
- name: Optionally add admin user to docker group (NOT recommended)
ansible.builtin.user:
@ -223,6 +224,14 @@
group: root
mode: "0755"
- name: Ensure /etc/nftables directory exists
ansible.builtin.file:
path: /etc/nftables
state: directory
owner: root
group: root
mode: "0755"
- name: Deploy nftables rules (hostfilter table)
ansible.builtin.template:
src: hostfilter.nft.j2
@ -247,6 +256,7 @@
enabled: true
state: started
daemon_reload: true
when: not ansible_check_mode
# Ensure fqdn -> public IPv4 mapping
- name: Ensure /etc/hosts has fqdn -> public IPv4 mapping
@ -271,14 +281,34 @@
mode: "0600"
when: manage_swapfile | bool
- name: Make swap
ansible.builtin.command: "mkswap {{ swapfile_path }}"
when: manage_swapfile | bool
- name: Check if swap is already active
ansible.builtin.command: "swapon --noheadings --show=NAME"
register: swapon_show
changed_when: false
- name: Enable swap
ansible.builtin.command: "swapon {{ swapfile_path }}"
failed_when: false
when: manage_swapfile | bool
- name: Check if swapfile already has swap signature
ansible.builtin.command: "blkid -o value -s TYPE {{ swapfile_path }}"
register: swapfile_type
changed_when: false
failed_when: false
when: manage_swapfile | bool
- name: Make swap (only if not already swap and not active)
ansible.builtin.command: "mkswap {{ swapfile_path }}"
when:
- manage_swapfile | bool
- (swapfile_type.stdout | default('')) != 'swap'
- (swapfile_path not in (swapon_show.stdout_lines | default([])))
# - (swapfile_path not in (swapon_show.stdout | default('')))
- name: Enable swap (only if not active)
ansible.builtin.command: "swapon {{ swapfile_path }}"
when:
- manage_swapfile | bool
- (swapfile_path not in (swapon_show.stdout_lines | default([])))
# - (swapfile_path not in (swapon_show.stdout | default('')))
failed_when: false
- name: Persist swap in fstab

View file

@ -25,7 +25,9 @@ table inet hostfilter {
iif "lo" accept
# Docker internal -> host (avoid breaking container->host access)
iifname { "docker0", "br-*" } accept
iifname "docker0" accept
iifname "br-*" accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
@ -57,19 +59,28 @@ table inet hostfilter {
ct state { established, related } accept
# Allow Docker internal forwarding (critical)
iifname { "docker0", "br-*" } oifname { "docker0", "br-*" } accept
iifname "docker0" oifname "docker0" accept
iifname "br-*" oifname "docker0" accept
iifname "docker0" oifname "br-*" accept
iifname "br-*" oifname "br-*" accept
# Allow containers to reach the Internet
iifname { "docker0", "br-*" } oifname $EXT_IF accept
iifname "docker0" oifname $EXT_IF accept
iifname "br-*" oifname $EXT_IF accept
# Allow Internet -> published container ports we expect (guardrail)
iifname $EXT_IF oifname { "docker0", "br-*" } tcp dport { 80, 443 } accept
iifname $EXT_IF oifname { "docker0", "br-*" } udp dport {{ bbb_udp_min }}-{{ bbb_udp_max }} accept
iifname $EXT_IF oifname "docker0" tcp dport { 80, 443 } accept
iifname $EXT_IF oifname "docker0" udp dport {{ bbb_udp_min }}-{{ bbb_udp_max }} accept
iifname $EXT_IF oifname "br-*" tcp dport { 80, 443 } accept
iifname $EXT_IF oifname "br-*" udp dport {{ bbb_udp_min }}-{{ bbb_udp_max }} accept
{% if turn_enabled | bool %}
iifname $EXT_IF oifname { "docker0", "br-*" } udp dport {{ turn_listen_port }} accept
iifname $EXT_IF oifname { "docker0", "br-*" } tcp dport { {{ turn_listen_port }}, {{ turn_tls_port }} } accept
iifname $EXT_IF oifname { "docker0", "br-*" } udp dport {{ turn_relay_udp_min }}-{{ turn_relay_udp_max }} accept
iifname $EXT_IF oifname "docker0" udp dport {{ turn_listen_port }} accept
iifname $EXT_IF oifname "br-*" udp dport {{ turn_listen_port }} accept
iifname $EXT_IF oifname "docker0" tcp dport { {{ turn_listen_port }}, {{ turn_tls_port }} } accept
iifname $EXT_IF oifname "br-*" tcp dport { {{ turn_listen_port }}, {{ turn_tls_port }} } accept
iifname $EXT_IF oifname "docker0" udp dport {{ turn_relay_udp_min }}-{{ turn_relay_udp_max }} accept
iifname $EXT_IF oifname "br-*" udp dport {{ turn_relay_udp_min }}-{{ turn_relay_udp_max }} accept
{% endif %}
limit rate 10/second burst 20 packets counter log prefix "nft-drop-fwd: " flags all drop

View file

@ -11,6 +11,8 @@ UsePAM yes
PubkeyAuthentication yes
AuthenticationMethods publickey
Subsystem sftp /usr/lib/openssh/sftp-server
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no