diff --git a/CHECKLIST_REBUILD.md b/CHECKLIST_REBUILD.md new file mode 100644 index 0000000..940899a --- /dev/null +++ b/CHECKLIST_REBUILD.md @@ -0,0 +1,38 @@ +# Checklist rebuild — VPS BBB (1 page) + +## Pré-vol (avant Ansible) +- [ ] DNS : `A` de `bbb.chezlepro.ca` pointe vers l’IPv4 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, 16384–32768/UDP + - TURN : 3478/TCP+UDP, 5349/TCP, relais UDP (ex: 50000–55000/UDP) + +## Smoke test (script) +- [ ] `sudo ./smoke.sh` retourne tout “OK” et n’affiche pas d’erreurs + +## Prochaine étape +- [ ] Installer BBB + coturn (rôle séparé) et refaire une checklist “WebRTC/BBB”. diff --git a/Makefile b/Makefile index e10ca70..d6d4305 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/Runbook_BBB_VPS_Ubuntu22.04.md b/Runbook_BBB_VPS_Ubuntu22.04.md new file mode 100644 index 0000000..02d114b --- /dev/null +++ b/Runbook_BBB_VPS_Ubuntu22.04.md @@ -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** +- L’install BBB viendra ensuite (BBB 3.x). + +--- + +## 2) Paramètres à personnaliser (avant exécution) + +### FQDN +- `fqdn: "bbb.chezlepro.ca"` + +### Compte d’exploitation 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 n’avez 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`, `16384–32768/udp` +- TURN : `3478/tcp+udp`, `5349/tcp`, relais UDP (plage à réduire) + +Recommandation : réduire le relais TURN à **50000–55000/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 d’installer BBB) + +1. Le DNS `A` de `bbb.chezlepro.ca` pointe vers l’IPv4 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 l’hô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 d’exécution (rebuild) + +### 5.1 Amorçage sudo (première exécution) +Si le user `ansible` n’a 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 l’auth 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 l’interpré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 Let’s 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). diff --git a/group_vars/all.yml b/group_vars/all.yml index ebb7fe5..7ef28f1 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -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 diff --git a/inventory.ini b/inventory.ini index 32b3458..deba735 100644 --- a/inventory.ini +++ b/inventory.ini @@ -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 + diff --git a/roles/bbb_vps_prep/handlers/main.yml b/roles/bbb_vps_prep/handlers/main.yml index 225dfff..0aa02c0 100644 --- a/roles/bbb_vps_prep/handlers/main.yml +++ b/roles/bbb_vps_prep/handlers/main.yml @@ -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 diff --git a/roles/bbb_vps_prep/tasks/main.yml b/roles/bbb_vps_prep/tasks/main.yml index bcb56c3..1c004c1 100644 --- a/roles/bbb_vps_prep/tasks/main.yml +++ b/roles/bbb_vps_prep/tasks/main.yml @@ -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 diff --git a/roles/bbb_vps_prep/templates/hostfilter.nft.j2 b/roles/bbb_vps_prep/templates/hostfilter.nft.j2 index 03a8ea2..29ed1d5 100644 --- a/roles/bbb_vps_prep/templates/hostfilter.nft.j2 +++ b/roles/bbb_vps_prep/templates/hostfilter.nft.j2 @@ -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 diff --git a/roles/bbb_vps_prep/templates/sshd_config.j2 b/roles/bbb_vps_prep/templates/sshd_config.j2 index f76da11..f4da217 100644 --- a/roles/bbb_vps_prep/templates/sshd_config.j2 +++ b/roles/bbb_vps_prep/templates/sshd_config.j2 @@ -11,6 +11,8 @@ UsePAM yes PubkeyAuthentication yes AuthenticationMethods publickey +Subsystem sftp /usr/lib/openssh/sftp-server + X11Forwarding no AllowAgentForwarding no AllowTcpForwarding no