--- - 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 check_mode: no 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 and not ansible_check_mode # --- 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 and not ansible_check_mode - 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: 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 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 when: not ansible_check_mode # 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: Check if swap is already active ansible.builtin.command: "swapon --noheadings --show=NAME" register: swapon_show changed_when: false 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 ansible.builtin.lineinfile: path: /etc/fstab line: "{{ swapfile_path }} none swap sw 0 0" state: present when: manage_swapfile | bool