Initial commit: BBB VPS prep (Ubuntu 22.04, nftables, Docker)
This commit is contained in:
commit
c59c5e9ef1
18 changed files with 680 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
*.retry
|
||||
*.log
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
.ansible/
|
||||
.cache/
|
||||
.venv/
|
||||
__pycache__/
|
||||
group_vars/*secret*.yml
|
||||
**/vault.yml
|
||||
.env
|
||||
57
Makefile
Normal file
57
Makefile
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
SHELL := /bin/bash
|
||||
|
||||
INVENTORY ?= inventory.ini
|
||||
PLAYBOOK ?= site.yml
|
||||
EXTRA_VARS ?=
|
||||
ANSIBLE ?= ansible-playbook
|
||||
|
||||
.PHONY: help ping check run dry-run diff vars tree clean
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " make ping - ping hosts (Ansible)"
|
||||
@echo " make check - syntax-check playbook"
|
||||
@echo " make run - apply playbook"
|
||||
@echo " make dry-run - check mode with diff (no changes)"
|
||||
@echo " make diff - show diff of changes (implies --check)"
|
||||
@echo " make vars - show vars for a host (requires host=...)"
|
||||
@echo " make tree - show repository tree"
|
||||
@echo ""
|
||||
@echo "Examples:"
|
||||
@echo " make run EXTRA_VARS='-e ssh_allow_cidrs_v4=[\"1.2.3.4/32\"]'"
|
||||
@echo " make vars host=bbb"
|
||||
|
||||
ping:
|
||||
ansible -i $(INVENTORY) vps -m ping
|
||||
|
||||
check:
|
||||
$(ANSIBLE) -i $(INVENTORY) $(PLAYBOOK) --syntax-check
|
||||
|
||||
run:
|
||||
$(ANSIBLE) -i $(INVENTORY) $(PLAYBOOK) --become $(EXTRA_VARS)
|
||||
|
||||
dry-run:
|
||||
$(ANSIBLE) -i $(INVENTORY) $(PLAYBOOK) --become --check --diff $(EXTRA_VARS)
|
||||
|
||||
diff:
|
||||
$(ANSIBLE) -i $(INVENTORY) $(PLAYBOOK) --become --check --diff $(EXTRA_VARS)
|
||||
|
||||
vars:
|
||||
@if [[ -z "$(host)" ]]; then echo "ERROR: host is required, e.g. make vars host=bbb"; exit 2; fi
|
||||
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
|
||||
|
||||
clean:
|
||||
@find . -name "*.retry" -delete
|
||||
36
README.md
Normal file
36
README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# bbb-vps-prep (Ubuntu 22.04) — nftables + hardening + Docker (BBB 3.x ready)
|
||||
|
||||
Ce dépôt prépare un VPS **Ubuntu 22.04** pour BigBlueButton (BBB 3.x) avec :
|
||||
|
||||
- SSH durci (clés seulement, root interdit, allowlist utilisateurs)
|
||||
- **nftables** comme firewall (pas UFW, pas iptables en gestion humaine)
|
||||
- Docker CE (repo officiel) requis par BBB 3.x
|
||||
- compatibilité Docker : on **n’efface jamais** le ruleset global, on applique une **table nftables dédiée** (`inet hostfilter`)
|
||||
- fail2ban avec action nftables
|
||||
- journald persistant + rotation
|
||||
- sysctl hardening (inclut ip_forward pour Docker)
|
||||
- swapfile (utile avec 12 Go RAM)
|
||||
|
||||
## Mise en route
|
||||
1) Éditer `inventory.ini` (IP du VPS)
|
||||
2) Éditer `group_vars/all.yml` (clé publique, CIDR SSH, FQDN)
|
||||
3) Exécuter :
|
||||
|
||||
```bash
|
||||
make check
|
||||
make run
|
||||
```
|
||||
|
||||
Mode dry-run :
|
||||
```bash
|
||||
make dry-run
|
||||
```
|
||||
|
||||
## Ports ouverts (par défaut)
|
||||
- SSH: 22/TCP (restreignable par CIDR)
|
||||
- BBB: 80/TCP, 443/TCP, 16384–32768/UDP
|
||||
- TURN (cohab): 3478/TCP+UDP, 5349/TCP, 32769–65535/UDP
|
||||
|
||||
## Notes
|
||||
- Docker gardera ses règles nécessaires au bridge/NAT; ce dépôt ajoute une couche “policy” via nftables.
|
||||
- TURN sur 443 “exclusif” n’est pas compatible cohabitation 1 IP avec nginx/BBB (on utilise 5349/TLS).
|
||||
41
group_vars/all.yml
Normal file
41
group_vars/all.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# --- Identity ---
|
||||
fqdn: "bbb.chezlepro.ca"
|
||||
|
||||
# --- Admin user + keys ---
|
||||
admin_user: "ansible"
|
||||
admin_pubkeys:
|
||||
- "ssh-ed25519 AAAA...REMPLACE... ton_key_ed25519 ..."
|
||||
|
||||
# --- 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_users: ["ansible"]
|
||||
|
||||
disable_password_auth: true
|
||||
permit_root_login: "no"
|
||||
|
||||
# --- BBB ports ---
|
||||
bbb_udp_min: 16384
|
||||
bbb_udp_max: 32768
|
||||
|
||||
# --- TURN cohabitation (1 IP) ---
|
||||
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
|
||||
|
||||
# --- Hardening toggles ---
|
||||
enable_unattended_upgrades: true
|
||||
enable_fail2ban: true
|
||||
journald_persistent: true
|
||||
|
||||
# --- Swap (utile avec 12G RAM) ---
|
||||
manage_swapfile: true
|
||||
swapfile_path: /swapfile
|
||||
swapfile_size_mb: 8192
|
||||
|
||||
# --- Docker (BBB 3.x requires latest docker) ---
|
||||
docker_install: true
|
||||
docker_add_admin_to_group: false # IMPORTANT: groupe docker = root-equivalent
|
||||
2
inventory.ini
Normal file
2
inventory.ini
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[vps]
|
||||
bbb ansible_host=YOUR.VPS.IP ansible_user=ansible
|
||||
9
roles/bbb_vps_prep/files/apply-hostfilter-nft.sh
Executable file
9
roles/bbb_vps_prep/files/apply-hostfilter-nft.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NFT=/usr/sbin/nft
|
||||
RULES=/etc/nftables/hostfilter.nft
|
||||
|
||||
# Delete only our table if present; do NOT flush whole ruleset (Docker owns its rules)
|
||||
$NFT delete table inet hostfilter 2>/dev/null || true
|
||||
$NFT -f "$RULES"
|
||||
31
roles/bbb_vps_prep/handlers/main.yml
Normal file
31
roles/bbb_vps_prep/handlers/main.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
- name: reload sshd
|
||||
ansible.builtin.service:
|
||||
name: ssh
|
||||
state: reloaded
|
||||
|
||||
- name: restart fail2ban
|
||||
ansible.builtin.service:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
|
||||
- name: restart journald
|
||||
ansible.builtin.service:
|
||||
name: systemd-journald
|
||||
state: restarted
|
||||
|
||||
- name: reload sysctl
|
||||
ansible.builtin.command: sysctl --system
|
||||
changed_when: false
|
||||
|
||||
- name: restart docker
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
|
||||
- name: restart hostfilter firewall
|
||||
ansible.builtin.systemd:
|
||||
name: hostfilter-nft
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
289
roles/bbb_vps_prep/tasks/main.yml
Normal file
289
roles/bbb_vps_prep/tasks/main.yml
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
---
|
||||
- name: Assert Ubuntu 22.04
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_distribution == "Ubuntu"
|
||||
- ansible_distribution_version is version("22.04", "==")
|
||||
fail_msg: "Ce playbook cible Ubuntu 22.04 LTS uniquement."
|
||||
|
||||
- name: Set hostname to FQDN
|
||||
ansible.builtin.hostname:
|
||||
name: "{{ fqdn }}"
|
||||
|
||||
- name: Install base packages
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- sudo
|
||||
- vim
|
||||
- jq
|
||||
- unzip
|
||||
- chrony
|
||||
- rsyslog
|
||||
- openssh-server
|
||||
- fail2ban
|
||||
- unattended-upgrades
|
||||
- apt-listchanges
|
||||
- logrotate
|
||||
- apparmor
|
||||
- apparmor-utils
|
||||
- nftables
|
||||
- locales
|
||||
- lsb-release
|
||||
state: present
|
||||
|
||||
# BBB 3.x install guide expects en_US.UTF-8
|
||||
- name: Ensure en_US.UTF-8 locale exists
|
||||
ansible.builtin.command: locale-gen en_US.UTF-8
|
||||
changed_when: false
|
||||
|
||||
- name: Set default locale
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/default/locale
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
LANG="en_US.UTF-8"
|
||||
|
||||
- name: Ensure admin user exists
|
||||
ansible.builtin.user:
|
||||
name: "{{ admin_user }}"
|
||||
groups: sudo
|
||||
append: true
|
||||
shell: /bin/bash
|
||||
create_home: true
|
||||
state: present
|
||||
|
||||
- name: Install admin authorized keys
|
||||
ansible.builtin.authorized_key:
|
||||
user: "{{ admin_user }}"
|
||||
key: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ admin_pubkeys }}"
|
||||
|
||||
- name: Harden sudoers for admin (NOPASSWD)
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/90-{{ admin_user }}"
|
||||
content: "{{ admin_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
|
||||
# --- SSH hardening ---
|
||||
- name: Template sshd_config
|
||||
ansible.builtin.template:
|
||||
src: sshd_config.j2
|
||||
dest: /etc/ssh/sshd_config
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: reload sshd
|
||||
|
||||
- name: Validate sshd configuration
|
||||
ansible.builtin.command: sshd -t
|
||||
changed_when: false
|
||||
|
||||
# --- journald persistence ---
|
||||
- name: Configure journald
|
||||
ansible.builtin.template:
|
||||
src: journald.conf.j2
|
||||
dest: /etc/systemd/journald.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart journald
|
||||
|
||||
- name: Ensure journald persistent directory exists
|
||||
ansible.builtin.file:
|
||||
path: /var/log/journal
|
||||
state: directory
|
||||
owner: root
|
||||
group: systemd-journal
|
||||
mode: "2755"
|
||||
when: journald_persistent | bool
|
||||
|
||||
# --- sysctl hardening (incl. ip_forward for Docker) ---
|
||||
- name: Apply sysctl hardening
|
||||
ansible.builtin.template:
|
||||
src: sysctl-hardening.conf.j2
|
||||
dest: /etc/sysctl.d/99-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload sysctl
|
||||
|
||||
# --- Unattended upgrades ---
|
||||
- name: Configure 20auto-upgrades
|
||||
ansible.builtin.template:
|
||||
src: 20auto-upgrades.j2
|
||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: enable_unattended_upgrades | bool
|
||||
|
||||
- name: Configure 50unattended-upgrades
|
||||
ansible.builtin.template:
|
||||
src: 50unattended-upgrades.j2
|
||||
dest: /etc/apt/apt.conf.d/50unattended-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: enable_unattended_upgrades | bool
|
||||
|
||||
# --- fail2ban (nftables action) ---
|
||||
- name: Configure fail2ban jail.local
|
||||
ansible.builtin.template:
|
||||
src: jail.local.j2
|
||||
dest: /etc/fail2ban/jail.local
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: enable_fail2ban | bool
|
||||
notify: restart fail2ban
|
||||
|
||||
- name: Ensure fail2ban enabled
|
||||
ansible.builtin.service:
|
||||
name: fail2ban
|
||||
enabled: true
|
||||
state: started
|
||||
when: enable_fail2ban | bool
|
||||
|
||||
# --- Docker CE (repo officiel) ---
|
||||
- name: Map architecture for Docker repo
|
||||
ansible.builtin.set_fact:
|
||||
docker_arch: >-
|
||||
{{
|
||||
{'x86_64':'amd64','aarch64':'arm64','armv7l':'armhf'}.get(ansible_architecture, 'amd64')
|
||||
}}
|
||||
when: docker_install | bool
|
||||
|
||||
- name: Add Docker GPG key (keyring)
|
||||
ansible.builtin.shell: |
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
args:
|
||||
creates: /etc/apt/keyrings/docker.gpg
|
||||
when: docker_install | bool
|
||||
|
||||
- name: Add Docker apt repo
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch={{ docker_arch }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
filename: docker
|
||||
state: present
|
||||
when: docker_install | bool
|
||||
|
||||
- name: Install Docker CE packages
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
when: docker_install | bool
|
||||
|
||||
- name: Configure Docker daemon.json
|
||||
ansible.builtin.template:
|
||||
src: docker-daemon.json.j2
|
||||
dest: /etc/docker/daemon.json
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: docker_install | bool
|
||||
notify: restart docker
|
||||
|
||||
- name: Enable and start Docker
|
||||
ansible.builtin.service:
|
||||
name: docker
|
||||
enabled: true
|
||||
state: started
|
||||
when: docker_install | bool
|
||||
|
||||
- name: Optionally add admin user to docker group (NOT recommended)
|
||||
ansible.builtin.user:
|
||||
name: "{{ admin_user }}"
|
||||
groups: docker
|
||||
append: true
|
||||
when: docker_install | bool and docker_add_admin_to_group | bool
|
||||
|
||||
# --- Host firewall (nftables policy layer; Docker keeps its own rules) ---
|
||||
- name: Install apply script for nftables hostfilter
|
||||
ansible.builtin.copy:
|
||||
src: apply-hostfilter-nft.sh
|
||||
dest: /usr/local/sbin/apply-hostfilter-nft
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy nftables rules (hostfilter table)
|
||||
ansible.builtin.template:
|
||||
src: hostfilter.nft.j2
|
||||
dest: /etc/nftables/hostfilter.nft
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart hostfilter firewall
|
||||
|
||||
- name: Deploy systemd unit for hostfilter firewall
|
||||
ansible.builtin.template:
|
||||
src: hostfilter-nft.service.j2
|
||||
dest: /etc/systemd/system/hostfilter-nft.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart hostfilter firewall
|
||||
|
||||
- name: Enable and start hostfilter firewall
|
||||
ansible.builtin.systemd:
|
||||
name: hostfilter-nft
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
# Ensure fqdn -> public IPv4 mapping
|
||||
- name: Ensure /etc/hosts has fqdn -> public IPv4 mapping
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: '^\s*{{ ansible_default_ipv4.address | regex_escape() }}\s+{{ fqdn | regex_escape() }}\s*$'
|
||||
line: "{{ ansible_default_ipv4.address }} {{ fqdn }}"
|
||||
state: present
|
||||
|
||||
# --- Swap (useful with 12G RAM) ---
|
||||
- name: Create swapfile
|
||||
ansible.builtin.command: "fallocate -l {{ swapfile_size_mb }}M {{ swapfile_path }}"
|
||||
args:
|
||||
creates: "{{ swapfile_path }}"
|
||||
when: manage_swapfile | bool
|
||||
|
||||
- name: Set swapfile permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ swapfile_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
when: manage_swapfile | bool
|
||||
|
||||
- name: Make swap
|
||||
ansible.builtin.command: "mkswap {{ swapfile_path }}"
|
||||
when: manage_swapfile | bool
|
||||
changed_when: false
|
||||
|
||||
- name: Enable swap
|
||||
ansible.builtin.command: "swapon {{ swapfile_path }}"
|
||||
when: manage_swapfile | bool
|
||||
failed_when: false
|
||||
|
||||
- name: Persist swap in fstab
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/fstab
|
||||
line: "{{ swapfile_path }} none swap sw 0 0"
|
||||
state: present
|
||||
when: manage_swapfile | bool
|
||||
5
roles/bbb_vps_prep/templates/20auto-upgrades.j2
Normal file
5
roles/bbb_vps_prep/templates/20auto-upgrades.j2
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Managed by Ansible
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::Download-Upgradeable-Packages "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
10
roles/bbb_vps_prep/templates/50unattended-upgrades.j2
Normal file
10
roles/bbb_vps_prep/templates/50unattended-upgrades.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Managed by Ansible
|
||||
Unattended-Upgrade::Origins-Pattern {
|
||||
"origin=Ubuntu,codename=${distro_codename}-security";
|
||||
"origin=Ubuntu,codename=${distro_codename}-updates";
|
||||
};
|
||||
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::SyslogEnable "true";
|
||||
7
roles/bbb_vps_prep/templates/docker-daemon.json.j2
Normal file
7
roles/bbb_vps_prep/templates/docker-daemon.json.j2
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"ip-forward-no-drop": true,
|
||||
"log-driver": "local",
|
||||
"log-opts": { "max-size": "50m", "max-file": "3" },
|
||||
"live-restore": true,
|
||||
"userland-proxy": false
|
||||
}
|
||||
12
roles/bbb_vps_prep/templates/hostfilter-nft.service.j2
Normal file
12
roles/bbb_vps_prep/templates/hostfilter-nft.service.j2
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Unit]
|
||||
Description=Host firewall (nftables) - hostfilter table
|
||||
Wants=network-online.target
|
||||
After=network-online.target docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/apply-hostfilter-nft
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
81
roles/bbb_vps_prep/templates/hostfilter.nft.j2
Normal file
81
roles/bbb_vps_prep/templates/hostfilter.nft.j2
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/sbin/nft -f
|
||||
|
||||
define EXT_IF = "{{ ansible_default_ipv4.interface }}"
|
||||
|
||||
table inet hostfilter {
|
||||
|
||||
set ssh_allow_v4 {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
elements = { {% for c in ssh_allow_cidrs_v4 %} {{ c }}{% if not loop.last %}, {% endif %}{% endfor %} }
|
||||
}
|
||||
|
||||
set ssh_allow_v6 {
|
||||
type ipv6_addr
|
||||
flags interval
|
||||
elements = { {% for c in ssh_allow_cidrs_v6 %} {{ c }}{% if not loop.last %}, {% endif %}{% endfor %} }
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
|
||||
ct state invalid drop
|
||||
ct state { established, related } accept
|
||||
|
||||
iif "lo" accept
|
||||
|
||||
# Docker internal -> host (avoid breaking container->host access)
|
||||
iifname { "docker0", "br-*" } accept
|
||||
|
||||
ip protocol icmp accept
|
||||
ip6 nexthdr icmpv6 accept
|
||||
|
||||
# SSH
|
||||
tcp dport {{ ssh_port }} ip saddr @ssh_allow_v4 accept
|
||||
tcp dport {{ ssh_port }} ip6 saddr @ssh_allow_v6 accept
|
||||
|
||||
# BBB
|
||||
tcp dport { 80, 443 } accept
|
||||
udp dport {{ bbb_udp_min }}-{{ bbb_udp_max }} accept
|
||||
|
||||
{% if turn_enabled | bool %}
|
||||
# TURN cohabitation (no 443)
|
||||
udp dport {{ turn_listen_port }} accept
|
||||
tcp dport {{ turn_listen_port }} accept
|
||||
tcp dport {{ turn_tls_port }} accept
|
||||
udp dport {{ turn_relay_udp_min }}-{{ turn_relay_udp_max }} accept
|
||||
{% endif %}
|
||||
|
||||
limit rate 10/second burst 20 packets counter log prefix "nft-drop-in: " flags all drop
|
||||
}
|
||||
|
||||
chain forward {
|
||||
# Run early; Docker keeps its chains, we enforce policy safely
|
||||
type filter hook forward priority -100; policy drop;
|
||||
|
||||
ct state invalid drop
|
||||
ct state { established, related } accept
|
||||
|
||||
# Allow Docker internal forwarding (critical)
|
||||
iifname { "docker0", "br-*" } oifname { "docker0", "br-*" } accept
|
||||
|
||||
# Allow containers to reach the Internet
|
||||
iifname { "docker0", "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
|
||||
|
||||
{% 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
|
||||
{% endif %}
|
||||
|
||||
limit rate 10/second burst 20 packets counter log prefix "nft-drop-fwd: " flags all drop
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority 0; policy accept;
|
||||
}
|
||||
}
|
||||
12
roles/bbb_vps_prep/templates/jail.local.j2
Normal file
12
roles/bbb_vps_prep/templates/jail.local.j2
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Managed by Ansible
|
||||
|
||||
[DEFAULT]
|
||||
backend = systemd
|
||||
banaction = nftables-multiport
|
||||
findtime = 10m
|
||||
bantime = 1h
|
||||
maxretry = 5
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = {{ ssh_port }}
|
||||
9
roles/bbb_vps_prep/templates/journald.conf.j2
Normal file
9
roles/bbb_vps_prep/templates/journald.conf.j2
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Managed by Ansible
|
||||
[Journal]
|
||||
Storage={{ "persistent" if journald_persistent else "auto" }}
|
||||
Compress=yes
|
||||
SystemMaxUse=500M
|
||||
RuntimeMaxUse=200M
|
||||
MaxRetentionSec=1month
|
||||
RateLimitIntervalSec=30s
|
||||
RateLimitBurst=10000
|
||||
30
roles/bbb_vps_prep/templates/sshd_config.j2
Normal file
30
roles/bbb_vps_prep/templates/sshd_config.j2
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Managed by Ansible
|
||||
Port {{ ssh_port }}
|
||||
Protocol 2
|
||||
|
||||
PermitRootLogin {{ permit_root_login }}
|
||||
PasswordAuthentication {{ "no" if disable_password_auth else "yes" }}
|
||||
KbdInteractiveAuthentication no
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM yes
|
||||
|
||||
PubkeyAuthentication yes
|
||||
AuthenticationMethods publickey
|
||||
|
||||
X11Forwarding no
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
PermitTunnel no
|
||||
PermitUserEnvironment no
|
||||
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
LoginGraceTime 30
|
||||
MaxAuthTries 3
|
||||
MaxSessions 4
|
||||
|
||||
LogLevel VERBOSE
|
||||
|
||||
{% if ssh_allow_users is defined and ssh_allow_users|length > 0 %}
|
||||
AllowUsers {% for u in ssh_allow_users %}{{ u }} {% endfor %}
|
||||
{% endif %}
|
||||
30
roles/bbb_vps_prep/templates/sysctl-hardening.conf.j2
Normal file
30
roles/bbb_vps_prep/templates/sysctl-hardening.conf.j2
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Managed by Ansible
|
||||
# Network hardening + Docker needs forwarding
|
||||
|
||||
net.ipv4.ip_forward = 1
|
||||
net.ipv6.conf.all.forwarding = 1
|
||||
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
|
||||
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.accept_source_route = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.default.accept_source_route = 0
|
||||
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
|
||||
fs.protected_hardlinks = 1
|
||||
fs.protected_symlinks = 1
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
kernel.randomize_va_space = 2
|
||||
|
||||
vm.swappiness = 10
|
||||
7
site.yml
Normal file
7
site.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
- name: Prepare Ubuntu 22.04 VPS for BBB (nftables + hardening + Docker)
|
||||
hosts: vps
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- bbb_vps_prep
|
||||
Loading…
Reference in a new issue