From 8bb06f3737f494f3751c4f017534641f1b94ce8d Mon Sep 17 00:00:00 2001 From: Nuxgrid Date: Wed, 22 Jul 2026 21:01:19 +0200 Subject: [PATCH] first commit --- .gitignore | 1 + ansible.cfg | 10 + hardening/playbook-aide-hardening.yml | 75 ++++ hardening/playbook-apparmor-hardening.yml | 83 +++++ hardening/playbook-audit-rules-cis-rest.yml | 73 ++++ hardening/playbook-audit-rules-sudo-omnex.yml | 84 +++++ .../playbook-coredump-auditd-hardening.yml | 117 ++++++ .../playbook-cron-packages-hardening.yml | 96 +++++ .../playbook-kernel-sysctl-hardening.yml | 71 ++++ hardening/playbook-pam-hardening.yml | 175 +++++++++ hardening/playbook-ssh-hardenning.yml | 76 ++++ infra/playbook-certbot.yml | 127 +++++++ infra/playbook-clamav.yml | 106 ++++++ infra/playbook-gitea-runner.yml | 332 ++++++++++++++++++ infra/playbook-s3-backup.yml | 146 ++++++++ infra/playbook-s3.yml | 146 ++++++++ infra/playbook-security.yml | 155 ++++++++ infra/playbook-unattended-upgrades.yml | 77 ++++ infra/playbook-wireguard-vpn.yml | 132 +++++++ inventory/group_vars/all/legacy_vars.yml | 15 + inventory/group_vars/all/vars.yml | 14 + inventory/group_vars/bdd-redis-prod/vars.yml | 40 +++ inventory/group_vars/infra-runner/vars.yml | 4 + inventory/group_vars/infra/vars.yml | 4 + inventory/group_vars/load-balancer/vars.yml | 9 + inventory/group_vars/pre-prod/vars.yml | 3 + inventory/group_vars/prod/vars.yml | 7 + inventory/group_vars/replica-prod/vars.yml | 4 + inventory/host.ini | 36 ++ inventory/host_vars/monitoring-uber.yml | 9 + pre-prod/playbook-pre-prod.yml | 164 +++++++++ prod/playbook-bdd-redis.yml | 308 ++++++++++++++++ prod/playbook-prod.yml | 172 +++++++++ prod/playbook-s3-img.yml | 155 ++++++++ templates/60-cis-coredump-limits.conf.j2 | 2 + templates/60-cis-coredump-sysctl.conf.j2 | 2 + templates/60-cis-network-hardening.conf.j2 | 29 ++ templates/72_aide_infra_excludes.j2 | 18 + templates/audit-50-sudo.rules.j2 | 12 + templates/audit-51-omnex.rules.j2 | 5 + templates/audit-52-cis-rest.rules.j2 | 76 ++++ templates/blacklist-unused-modules.conf.j2 | 33 ++ templates/cloud_harden.conf.j2 | 1 + templates/faillock.conf.j2 | 5 + templates/haproxy.cfg.j2 | 48 +++ templates/pam-configs-faillock-notify.j2 | 9 + templates/pam-configs-faillock.j2 | 6 + templates/pam-configs-pwhistory.j2 | 6 + templates/pam-configs-pwquality.j2 | 7 + templates/pam-configs-unix.j2 | 23 ++ templates/pwquality.conf.j2 | 8 + templates/ssh_harden_conf.j2 | 123 +++++++ templates/unattended-upgrades-20.j2 | 5 + templates/unattended-upgrades-50.j2 | 30 ++ .../unattended-upgrades-notify.service.j2 | 7 + templates/unattended-upgrades-notify.sh.j2 | 38 ++ templates/unattended-upgrades-notify.timer.j2 | 10 + 57 files changed, 3529 insertions(+) create mode 100644 .gitignore create mode 100644 ansible.cfg create mode 100644 hardening/playbook-aide-hardening.yml create mode 100644 hardening/playbook-apparmor-hardening.yml create mode 100644 hardening/playbook-audit-rules-cis-rest.yml create mode 100644 hardening/playbook-audit-rules-sudo-omnex.yml create mode 100644 hardening/playbook-coredump-auditd-hardening.yml create mode 100644 hardening/playbook-cron-packages-hardening.yml create mode 100644 hardening/playbook-kernel-sysctl-hardening.yml create mode 100644 hardening/playbook-pam-hardening.yml create mode 100644 hardening/playbook-ssh-hardenning.yml create mode 100644 infra/playbook-certbot.yml create mode 100644 infra/playbook-clamav.yml create mode 100644 infra/playbook-gitea-runner.yml create mode 100644 infra/playbook-s3-backup.yml create mode 100644 infra/playbook-s3.yml create mode 100644 infra/playbook-security.yml create mode 100644 infra/playbook-unattended-upgrades.yml create mode 100644 infra/playbook-wireguard-vpn.yml create mode 100644 inventory/group_vars/all/legacy_vars.yml create mode 100644 inventory/group_vars/all/vars.yml create mode 100644 inventory/group_vars/bdd-redis-prod/vars.yml create mode 100644 inventory/group_vars/infra-runner/vars.yml create mode 100644 inventory/group_vars/infra/vars.yml create mode 100644 inventory/group_vars/load-balancer/vars.yml create mode 100644 inventory/group_vars/pre-prod/vars.yml create mode 100644 inventory/group_vars/prod/vars.yml create mode 100644 inventory/group_vars/replica-prod/vars.yml create mode 100644 inventory/host.ini create mode 100644 inventory/host_vars/monitoring-uber.yml create mode 100644 pre-prod/playbook-pre-prod.yml create mode 100644 prod/playbook-bdd-redis.yml create mode 100644 prod/playbook-prod.yml create mode 100644 prod/playbook-s3-img.yml create mode 100644 templates/60-cis-coredump-limits.conf.j2 create mode 100644 templates/60-cis-coredump-sysctl.conf.j2 create mode 100644 templates/60-cis-network-hardening.conf.j2 create mode 100644 templates/72_aide_infra_excludes.j2 create mode 100644 templates/audit-50-sudo.rules.j2 create mode 100644 templates/audit-51-omnex.rules.j2 create mode 100644 templates/audit-52-cis-rest.rules.j2 create mode 100644 templates/blacklist-unused-modules.conf.j2 create mode 100644 templates/cloud_harden.conf.j2 create mode 100644 templates/faillock.conf.j2 create mode 100644 templates/haproxy.cfg.j2 create mode 100644 templates/pam-configs-faillock-notify.j2 create mode 100644 templates/pam-configs-faillock.j2 create mode 100644 templates/pam-configs-pwhistory.j2 create mode 100644 templates/pam-configs-pwquality.j2 create mode 100644 templates/pam-configs-unix.j2 create mode 100644 templates/pwquality.conf.j2 create mode 100644 templates/ssh_harden_conf.j2 create mode 100644 templates/unattended-upgrades-20.j2 create mode 100644 templates/unattended-upgrades-50.j2 create mode 100644 templates/unattended-upgrades-notify.service.j2 create mode 100644 templates/unattended-upgrades-notify.sh.j2 create mode 100644 templates/unattended-upgrades-notify.timer.j2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6b130c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/settings.local.json diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..9d20abc --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,10 @@ +[defaults] +inventory = inventory/host.ini +host_key_checking = False +interpreter_python = auto_silent +vars = groups_vars/ + +[privilege_escalation] +become = True +become_method = sudo +become_user = root diff --git a/hardening/playbook-aide-hardening.yml b/hardening/playbook-aide-hardening.yml new file mode 100644 index 0000000..7e99a71 --- /dev/null +++ b/hardening/playbook-aide-hardening.yml @@ -0,0 +1,75 @@ +--- +# Durcissement CIS Ubuntu 24.04 — AIDE (integrite fichiers) installe + verification quotidienne +# (checks 35758, 35759) +# +# IMPORTANT : /var/lib/docker (images/layers/volumes), les donnees de bases de donnees +# (postgresql, redis), wazuh-indexer, clamav et /var/log/audit sont exclus AVANT le premier +# aideinit — sans ca, le scan initial serait tres long et generait des faux positifs +# quotidiens (ces donnees changent en permanence par nature, leur integrite est geree par +# leurs propres mecanismes, pas par un FIM au niveau fichier). +# +# `aideinit` peut prendre plusieurs minutes (hash de la quasi-totalite du systeme de +# fichiers) — pas de reboot necessaire, mais consommation CPU/IO notable pendant le scan. +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-aide-hardening.yml --limit pre-prod-uber + +- name: Durcissement AIDE - integrite fichiers (CIS Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + - name: Installer aide et aide-common + ansible.builtin.apt: + name: + - aide + - aide-common + state: present + update_cache: true + cache_valid_time: 3600 + + - name: Deployer les exclusions specifiques infra (avant le premier scan) + ansible.builtin.template: + src: ../templates/72_aide_infra_excludes.j2 + dest: /etc/aide/aide.conf.d/72_aide_infra_excludes + owner: root + group: root + mode: "0644" + + - name: Verifier si une base AIDE existe deja + ansible.builtin.stat: + path: /var/lib/aide/aide.db + register: aide_db + + - name: Initialiser la base AIDE (aideinit) — peut prendre plusieurs minutes + ansible.builtin.command: + cmd: aideinit -y -f + when: not aide_db.stat.exists + changed_when: true + + - name: Activer et demarrer la verification quotidienne (dailyaidecheck.timer) + ansible.builtin.systemd: + name: "{{ item }}" + masked: false + enabled: true + state: started + loop: + - dailyaidecheck.service + - dailyaidecheck.timer + + - name: Verifier l'etat final + ansible.builtin.command: + cmd: systemctl show dailyaidecheck.timer -p LoadState,ActiveState + register: aide_timer_check + changed_when: false + failed_when: > + 'LoadState=loaded' not in aide_timer_check.stdout or + 'ActiveState=active' not in aide_timer_check.stdout + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: "✅ AIDE installé et vérification quotidienne active sur {{ inventory_hostname }}." diff --git a/hardening/playbook-apparmor-hardening.yml b/hardening/playbook-apparmor-hardening.yml new file mode 100644 index 0000000..a85b706 --- /dev/null +++ b/hardening/playbook-apparmor-hardening.yml @@ -0,0 +1,83 @@ +--- +# Durcissement CIS Ubuntu 24.04 — AppArmor installe, active au boot, tous les profils enforcing +# (checks 35536-35539) +# +# CONTEXTE (verifie manuellement avant deploiement) : sur cette infra, apparmor_status montre +# des dizaines de profils en mode "unconfined"/"complain" qui correspondent tous a des paquets +# desktop jamais utilises sur des serveurs headless (navigateurs, Discord, Steam, transmission, +# runtimes conteneurs standalone comme runc/crun/podman non utilises directement — Docker confine +# deja ses containers via le profil "docker-default", separe et deja enforce). Aucun process +# reellement actif n'est concerne par le passage en enforce de ces profils inutilises. +# +# IMPORTANT GRUB : /etc/default/grub contient deja "audit=1 audit_backlog_limit=8192" +# (playbook-coredump-auditd-hardening.yml) — ce playbook AJOUTE apparmor=1 security=apparmor +# a la suite, ne remplace jamais la ligne existante. +# +# `aa-enforce /etc/apparmor.d/*` s'applique a chaud (pas de redemarrage necessaire pour le +# check CIS lui-meme), mais comme pour audit=1, le parametre GRUB apparmor=1/security=apparmor +# necessite un redemarrage pour etre reellement actif au niveau noyau (le check 35537 passe +# immediatement car il inspecte /etc/default/grub, pas l'etat runtime). +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-apparmor-hardening.yml --limit pre-prod-uber + +- name: Durcissement AppArmor (CIS Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + - name: Installer apparmor et apparmor-utils + ansible.builtin.apt: + name: + - apparmor + - apparmor-utils + state: present + update_cache: true + cache_valid_time: 3600 + + - name: Sauvegarder /etc/default/grub avant modification + ansible.builtin.copy: + src: /etc/default/grub + dest: /etc/default/grub.bak-cis-apparmor + remote_src: true + owner: root + group: root + mode: "0644" + + - name: Ajouter apparmor=1 security=apparmor a GRUB_CMDLINE_LINUX (sans ecraser audit=1 existant) + ansible.builtin.shell: | + set -e + if ! grep -q 'apparmor=1' /etc/default/grub; then + sed -i 's/^\(GRUB_CMDLINE_LINUX="[^"]*\)"/\1 apparmor=1 security=apparmor"/' /etc/default/grub + echo CHANGED + fi + register: grub_conf + changed_when: "'CHANGED' in grub_conf.stdout" + + - name: Regenerer grub.cfg si la config a change + ansible.builtin.command: + cmd: update-grub + when: grub_conf.changed + changed_when: true + + - name: Forcer tous les profils AppArmor en mode enforce + ansible.builtin.command: + cmd: aa-enforce /etc/apparmor.d/* + changed_when: true + + - name: Verifier l'etat final (apparmor_status) + ansible.builtin.command: + cmd: apparmor_status + register: aa_status + changed_when: false + failed_when: > + 'profiles are in complain mode' in aa_status.stdout and + not (aa_status.stdout | regex_search('^0\s*profiles are in complain mode', multiline=True)) + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: "✅ AppArmor durci sur {{ inventory_hostname }} — reboot requis pour apparmor=1/security=apparmor au niveau noyau." diff --git a/hardening/playbook-audit-rules-cis-rest.yml b/hardening/playbook-audit-rules-cis-rest.yml new file mode 100644 index 0000000..94b4856 --- /dev/null +++ b/hardening/playbook-audit-rules-cis-rest.yml @@ -0,0 +1,73 @@ +--- +# Regles d'audit detaillees restantes (CIS 6.2.3.4-6.2.3.19) — appliquees a chaud, pas de reboot +# +# PREREQUIS : hardening/playbook-coredump-auditd-hardening.yml (auditd) et +# hardening/playbook-audit-rules-sudo-omnex.yml deja appliques. +# +# Couvre : date/heure, environnement reseau, acces fichier echoues, user/group (identity), +# permissions DAC (chmod/chown/xattr), montages, sessions, login/logout, suppressions fichiers, +# politique MAC (AppArmor), commandes chcon/setfacl/chacl/usermod, modules noyau. +# +# UID_MIN lu dynamiquement depuis /etc/login.defs de chaque host (1000 sur cette infra, mais +# pas suppose en dur pour rester correct si un host differe). +# +# `augenrules --load` recharge a chaud : PAS de redemarrage necessaire tant que le mode +# immutable (-e 2, check 35749) n'est pas active — volontairement PAS fait par ce playbook. +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-audit-rules-cis-rest.yml --limit pre-prod-uber + +- name: Regles d'audit detaillees restantes CIS 6.2.3.x (Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + - name: Lire UID_MIN depuis /etc/login.defs + ansible.builtin.command: + cmd: awk '/^\s*UID_MIN/{print $2}' /etc/login.defs + register: uid_min_result + changed_when: false + + - name: Deployer les regles d'audit CIS restantes + ansible.builtin.template: + src: ../templates/audit-52-cis-rest.rules.j2 + dest: /etc/audit/rules.d/52-cis-rest.rules + owner: root + group: root + mode: "0640" + vars: + uid_min: "{{ uid_min_result.stdout }}" + register: cis_rest_rules + + - name: Charger les nouvelles regles d'audit (augenrules --load) + ansible.builtin.command: + cmd: augenrules --load + when: cis_rest_rules.changed + changed_when: true + + - name: Verifier que les cles principales sont actives (auditctl -l) + ansible.builtin.command: + cmd: auditctl -l + register: auditctl_check + changed_when: false + failed_when: > + 'time-change' not in auditctl_check.stdout or + 'system-locale' not in auditctl_check.stdout or + 'identity' not in auditctl_check.stdout or + 'perm_mod' not in auditctl_check.stdout or + 'mounts' not in auditctl_check.stdout or + 'session' not in auditctl_check.stdout or + 'logins' not in auditctl_check.stdout or + 'delete' not in auditctl_check.stdout or + 'MAC-policy' not in auditctl_check.stdout or + 'perm_chng' not in auditctl_check.stdout or + 'usermod' not in auditctl_check.stdout or + 'kernel_modules' not in auditctl_check.stdout + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: "✅ Règles d'audit CIS 6.2.3.x restantes actives sur {{ inventory_hostname }} (UID_MIN={{ uid_min_result.stdout }}, pas de reboot requis)." diff --git a/hardening/playbook-audit-rules-sudo-omnex.yml b/hardening/playbook-audit-rules-sudo-omnex.yml new file mode 100644 index 0000000..abe8832 --- /dev/null +++ b/hardening/playbook-audit-rules-sudo-omnex.yml @@ -0,0 +1,84 @@ +--- +# Regles d'audit detaillees (CIS 6.2.3.1-6.2.3.3) — surveillance sudo + surveillance dediee omnex +# +# PREREQUIS : necessite hardening/playbook-coredump-auditd-hardening.yml deja applique +# (auditd installe et actif). +# +# - Regles CIS standard : changements /etc/sudoers(.d), toute action "en tant qu'un autre +# utilisateur" (execve avec euid!=uid, couvre sudo/su), modifications de /var/log/sudo.log. +# - Regle custom omnex : capture TOUTE commande executee par omnex (execve filtre sur auid, +# qui persiste a travers sudo/su, donc suit omnex meme apres elevation) — uniquement sur +# les hosts ou l'utilisateur omnex existe (absent sur bdd-redis-prod/backup-mln, root direct). +# - `augenrules --load` recharge la config a chaud : PAS besoin de redemarrage ici (contrairement +# a audit=1 dans GRUB) tant que le mode immutable (-e 2) n'est pas active — volontairement pas +# configure par ce playbook (risque : plus aucune modif de regle possible sans reboot). +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-audit-rules-sudo-omnex.yml --limit pre-prod-uber + +- name: Regles d'audit detaillees - sudo + omnex (CIS Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + - name: Deployer les regles d'audit sudo (CIS 6.2.3.1-6.2.3.3) + ansible.builtin.template: + src: ../templates/audit-50-sudo.rules.j2 + dest: /etc/audit/rules.d/50-sudo.rules + owner: root + group: root + mode: "0640" + register: sudo_rules + + - name: Verifier si l'utilisateur omnex existe sur cet host + ansible.builtin.getent: + database: passwd + key: omnex + ignore_errors: true + register: omnex_getent + + - name: Deployer la regle d'audit dediee omnex (uniquement si l'utilisateur existe) + ansible.builtin.template: + src: ../templates/audit-51-omnex.rules.j2 + dest: /etc/audit/rules.d/51-omnex.rules + owner: root + group: root + mode: "0640" + vars: + omnex_uid: "{{ ansible_facts.getent_passwd.omnex[1] }}" + when: omnex_getent is succeeded + register: omnex_rules + + - name: Charger les nouvelles regles d'audit (augenrules --load) + ansible.builtin.command: + cmd: augenrules --load + when: sudo_rules.changed or (omnex_rules is defined and omnex_rules.changed) + changed_when: true + + - name: Verifier que les regles sudo sont bien actives (auditctl -l) + ansible.builtin.command: + cmd: auditctl -l + register: auditctl_check + changed_when: false + failed_when: > + 'scope' not in auditctl_check.stdout or + 'user_emulation' not in auditctl_check.stdout or + 'sudo_log_file' not in auditctl_check.stdout + + - name: Verifier que la regle omnex est active (si applicable) + ansible.builtin.command: + cmd: auditctl -l + register: auditctl_omnex_check + changed_when: false + when: omnex_getent is succeeded + failed_when: "'omnex_actions' not in auditctl_omnex_check.stdout" + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: >- + ✅ Règles d'audit sudo{{ ' + omnex' if omnex_getent is succeeded else '' }} + actives sur {{ inventory_hostname }} (auditctl -l vérifié, pas de reboot requis). diff --git a/hardening/playbook-coredump-auditd-hardening.yml b/hardening/playbook-coredump-auditd-hardening.yml new file mode 100644 index 0000000..4e243fa --- /dev/null +++ b/hardening/playbook-coredump-auditd-hardening.yml @@ -0,0 +1,117 @@ +--- +# Durcissement CIS Ubuntu 24.04 — core dumps restreints, Apport désactivé, auditd installé/actif +# (checks 35543, 35545, 35723-35726, 35756, 35757) +# +# IMPORTANT — REBOOT : +# - audit=1 et audit_backlog_limit=8192 sont ajoutés à /etc/default/grub puis `update-grub` +# régénère immédiatement /boot/grub/grub.cfg → les checks CIS 35725/35726 passent au VERT +# dès ce playbook exécuté (ils inspectent le fichier grub.cfg généré, pas le noyau en cours +# d'exécution). +# - MAIS la protection réelle (auditer les process qui démarrent avant auditd) ne prend effet +# qu'après un REDÉMARRAGE du serveur (le paramètre kernel n'est actif qu'au prochain boot). +# Ce playbook NE redémarre PAS automatiquement les serveurs — décision à prendre séparément, +# en fenêtre de maintenance, host par host (impact Docker/VPN/prod public à chaque reboot). +# +# - apport est PURGÉ (pas juste désactivé) : cohérent avec le choix déjà fait pour rsync/telnet/ftp +# dans ce même chantier. apport-core-dump-handler et python3-apport partent avec (dépendances), +# sans impact sur les services applicatifs de cette infra (aucun ne dépend d'apport). +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-coredump-auditd-hardening.yml --limit pre-prod-uber + +- name: Durcissement core dumps + apport + auditd (CIS Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + # --- 1.5.3 Core dumps restreints --- + - name: Deployer fs.suid_dumpable=0 + ansible.builtin.template: + src: ../templates/60-cis-coredump-sysctl.conf.j2 + dest: /etc/sysctl.d/60-cis-coredump-sysctl.conf + owner: root + group: root + mode: "0644" + register: coredump_sysctl + + - name: Appliquer fs.suid_dumpable + ansible.builtin.command: + cmd: sysctl --system + when: coredump_sysctl.changed + changed_when: true + + - name: Deployer la limite hard core 0 + ansible.builtin.template: + src: ../templates/60-cis-coredump-limits.conf.j2 + dest: /etc/security/limits.d/60-cis-coredump.conf + owner: root + group: root + mode: "0644" + + # --- 1.5.5 Apport (Automatic Error Reporting) --- + - name: Purger apport (et ses dependances python3-apport, apport-core-dump-handler) + ansible.builtin.apt: + name: apport + state: absent + purge: true + + # --- 6.2.1.x auditd --- + - name: Installer auditd et audispd-plugins + ansible.builtin.apt: + name: + - auditd + - audispd-plugins + state: present + update_cache: true + cache_valid_time: 3600 + + - name: Demasquer, activer et demarrer auditd + ansible.builtin.systemd: + name: auditd + masked: false + enabled: true + state: started + + - name: Verifier la presence de audit=1 dans GRUB_CMDLINE_LINUX + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX=' + line: 'GRUB_CMDLINE_LINUX="audit=1 audit_backlog_limit=8192"' + backup: true + register: grub_conf + + - name: Regenerer grub.cfg si la config a change + ansible.builtin.command: + cmd: update-grub + when: grub_conf.changed + changed_when: true + + - name: Verifier que audit=1 est bien present dans grub.cfg genere + ansible.builtin.command: + cmd: grep -q 'audit=1' /boot/grub/grub.cfg + changed_when: false + + # --- 6.2.4.9 / 6.2.4.10 Ownership des outils d'audit --- + - name: Proprietaire/groupe root:root sur les outils d'audit + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + loop: + - /sbin/auditctl + - /sbin/aureport + - /sbin/ausearch + - /sbin/autrace + - /sbin/auditd + - /sbin/augenrules + ignore_errors: true # certains binaires peuvent etre des symlinks vers /usr/sbin selon la distro + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: >- + ✅ Core dumps/apport/auditd appliqués sur {{ inventory_hostname }}. + REDÉMARRAGE requis pour que audit=1 protège les process pré-auditd (non fait par ce playbook). diff --git a/hardening/playbook-cron-packages-hardening.yml b/hardening/playbook-cron-packages-hardening.yml new file mode 100644 index 0000000..dfd1273 --- /dev/null +++ b/hardening/playbook-cron-packages-hardening.yml @@ -0,0 +1,96 @@ +--- +# Durcissement CIS Ubuntu 24.04 — retrait rsync/telnet/ftp + permissions cron + cron.allow/at.allow +# (checks 35573, 35585, 35587, 35594-35601) +# +# IMPORTANT : +# - rsync/telnet/ftp verifies avant retrait sur cette infra : aucun daemon actif, aucun cron/script +# n'en dependait (grep sur les crontabs + scripts de backup). Purge sans risque constate. +# - Le paquet 'at' n'est PAS installe sur cette infra et ce playbook ne l'installe PAS : ajouter un +# service juste pour le verrouiller ensuite serait contre-productif (surface d'attaque en plus +# pour un compliance-checkbox). /etc/at.allow est quand meme cree en defense en profondeur, mais +# le check CIS 35601 restera en echec par ce choix assume tant que 'at' n'est pas installe. +# - /etc/cron.allow doit avoir owner:group EXACTEMENT root:root pour satisfaire le check automatise +# 35600 — la documentation CIS mentionne le groupe "crontab" comme alternative acceptable, mais la +# regex du check SCA (`r:^root root$`) n'accepte que root:root en pratique (verifie empiriquement). +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-cron-packages-hardening.yml --limit pre-prod-uber + +- name: Durcissement cron/at + retrait paquets insecurises (CIS Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + - name: Purger rsync, telnet et ftp (clients/services insecurises) + ansible.builtin.apt: + name: + - rsync + - telnet + - inetutils-telnet + - ftp + - tnftp + state: absent + purge: true + + - name: Permissions /etc/crontab (0600 root:root) + ansible.builtin.file: + path: /etc/crontab + owner: root + group: root + mode: "0600" + + - name: Permissions repertoires cron (0700 root:root) + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0700" + loop: + - /etc/cron.hourly + - /etc/cron.daily + - /etc/cron.weekly + - /etc/cron.monthly + - /etc/cron.d + + - name: Verifier si /etc/cron.deny existe + ansible.builtin.stat: + path: /etc/cron.deny + register: cron_deny + + - name: Creer /etc/cron.allow (crontab restreint aux utilisateurs autorises) + ansible.builtin.file: + path: /etc/cron.allow + state: touch + owner: root + group: root + mode: "0640" + access_time: preserve + modification_time: preserve + when: not cron_deny.stat.exists + + - name: Permissions /etc/cron.deny si present + ansible.builtin.file: + path: /etc/cron.deny + owner: root + group: root + mode: "0640" + when: cron_deny.stat.exists + + - name: Creer /etc/at.allow (defense en profondeur, meme si 'at' non installe) + ansible.builtin.file: + path: /etc/at.allow + state: touch + owner: root + group: daemon + mode: "0640" + access_time: preserve + modification_time: preserve + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: "✅ Durcissement cron/paquets appliqué sur {{ inventory_hostname }}" diff --git a/hardening/playbook-kernel-sysctl-hardening.yml b/hardening/playbook-kernel-sysctl-hardening.yml new file mode 100644 index 0000000..80ed2e7 --- /dev/null +++ b/hardening/playbook-kernel-sysctl-hardening.yml @@ -0,0 +1,71 @@ +--- +# Durcissement CIS Ubuntu 24.04 — modules noyau reseau/filesystem inutilises + sysctl reseau +# (checks 35509, 35604-35607, 35608-35618) +# +# IMPORTANT : +# - net.ipv4.ip_forward est laisse ACTIF sur la plupart des hosts (Docker gere le NAT des ports +# publies, vpn-uber fait relais entre pairs WireGuard) — le desactiver casserait ces usages. +# Seul load-balancer (pas de Docker, pas de relais VPN) le desactive, via la variable +# `ip_forwarding_disabled` (group_vars, defaut false, true uniquement pour load-balancer). +# Le check CIS 35608 restera donc en echec par necessite fonctionnelle sur la plupart des hosts. +# - rp_filter est mis a 1 (strict) partout : verifie sans casse sur cette infra (Docker + WireGuard +# coexistent sans probleme de routage asymetrique ici), mais a re-tester si la topologie change. +# - Aucun des modules blacklistes n'est charge sur cette infra (verifie via lsmod avant deploiement) : +# zero impact fonctionnel attendu. Exception : fat/fuse sont compiles en dur dans le noyau Ubuntu +# (builtin) et resteront "disponibles" quoi qu'il arrive — check CIS 35509 restera en echec pour +# ces deux modules specifiquement, cas documente par la CIS elle-meme comme sans remediation +# possible ("if pre-compiled into the kernel, no remediation is necessary"). +# +# `serial: 1` + `any_errors_fatal` : un host a la fois, arret immediat si un host casse. +# +# Usage recommande : tester sur un host avant le rollout complet : +# ansible-playbook hardening/playbook-kernel-sysctl-hardening.yml --limit pre-prod-uber + +- name: Durcissement kernel modules + sysctl reseau (CIS Ubuntu 24.04) + hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer + become: true + serial: 1 + any_errors_fatal: true + + tasks: + - name: Deployer le blacklist des modules noyau inutilises + ansible.builtin.template: + src: ../templates/blacklist-unused-modules.conf.j2 + dest: /etc/modprobe.d/cis-blacklist-unused.conf + owner: root + group: root + mode: "0644" + + - name: Deployer le durcissement sysctl reseau + ansible.builtin.template: + src: ../templates/60-cis-network-hardening.conf.j2 + dest: /etc/sysctl.d/60-cis-network-hardening.conf + owner: root + group: root + mode: "0644" + register: sysctl_conf + + - name: Appliquer les parametres sysctl actifs + ansible.builtin.command: + cmd: sysctl --system + when: sysctl_conf.changed + changed_when: true + register: sysctl_apply + + - name: Verifier qu'aucune erreur n'est remontee par sysctl --system + ansible.builtin.fail: + msg: "Erreur sysctl --system sur {{ inventory_hostname }} : {{ sysctl_apply.stdout }}" + when: sysctl_apply is defined and sysctl_apply.stdout is defined and 'error' in sysctl_apply.stdout | lower + + - name: Verifier la valeur effective de ip_forward + ansible.builtin.command: + cmd: sysctl -n net.ipv4.ip_forward + register: ip_forward_check + changed_when: false + failed_when: > + (not (ip_forwarding_disabled | default(false)) and ip_forward_check.stdout != "1") or + ((ip_forwarding_disabled | default(false)) and ip_forward_check.stdout != "0") + + - name: Confirmer le succes pour cet host + ansible.builtin.debug: + msg: "✅ Durcissement kernel/sysctl appliqué sur {{ inventory_hostname }} — ip_forward={{ ip_forward_check.stdout }}" diff --git a/hardening/playbook-pam-hardening.yml b/hardening/playbook-pam-hardening.yml new file mode 100644 index 0000000..ebf1b9a --- /dev/null +++ b/hardening/playbook-pam-hardening.yml @@ -0,0 +1,175 @@ +--- +# Durcissement PAM (CIS Ubuntu 24.04 — pam_faillock, pam_pwquality, pam_pwhistory) +# +# IMPORTANT — à lire avant d'exécuter : +# - Ce playbook touche l'authentification (sudo/su) sur TOUS les hosts ciblés. +# Une erreur dans un profil pam-auth-update peut casser sudo partout. +# - `serial: 1` traite un host à la fois : si un host casse, le run s'arrête +# avant de toucher les suivants (any_errors_fatal). +# - La dernière tâche force un reset du cache sudo (`sudo -K`) puis revalide +# un `become` réel, pour détecter une casse immédiatement plutôt que de la +# découvrir 15 min plus tard quand le cache sudo normal expire. +# - Volontairement AUCUNE expiration de mot de passe n'est appliquée sur les +# comptes existants (omnex/root) : ces comptes se connectent en SSH par clé +# (jamais par mot de passe), donc le mécanisme d'expiration PAM — qui ne se +# déclenche qu'au moment d'un prompt de mot de passe au login — ne se +# déclencherait jamais, et une expiration silencieuse casserait `sudo` sans +# aucun moyen interactif de le corriger à distance. PASS_MAX_DAYS n'est posé +# que dans /etc/login.defs (valeur par défaut pour les FUTURS comptes), pas +# rétroactivement via chage. +# +# Usage recommandé : tester d'abord sur UN seul host avant le rollout complet : +# ansible-playbook infra/playbook-pam-hardening.yml --limit pre-prod-uber +# +- name: Durcissement PAM (CIS Ubuntu 24.04) + hosts: bdd-redis-prod + become: true + serial: 1 + any_errors_fatal: true + vars: + pam_faillock_deny: 5 + pam_faillock_unlock_time: 900 + pam_faillock_fail_interval: 900 + pam_pwquality_minlen: 16 + pam_pwquality_difok: 8 + pam_pwhistory_remember: 24 + + tasks: + - name: Installer libpam-pwquality + ansible.builtin.apt: + name: libpam-pwquality + state: present + update_cache: true + cache_valid_time: 3600 + + - name: Sauvegarder l'état PAM actuel avant modification + ansible.builtin.shell: | + set -e + BACKUP_DIR="/root/pam-backup-$(date +%Y%m%d%H%M%S)" + mkdir -p "$BACKUP_DIR" + cp -a /etc/pam.d "$BACKUP_DIR/" + cp -a /usr/share/pam-configs "$BACKUP_DIR/" + cp /etc/login.defs "$BACKUP_DIR/" 2>/dev/null || true + [ -f /etc/security/faillock.conf ] && cp /etc/security/faillock.conf "$BACKUP_DIR/" || true + [ -f /etc/security/pwquality.conf ] && cp /etc/security/pwquality.conf "$BACKUP_DIR/" || true + echo "$BACKUP_DIR" + args: + executable: /bin/bash + changed_when: false + register: pam_backup + + - name: Afficher le chemin de sauvegarde (rollback manuel possible) + ansible.builtin.debug: + msg: "Backup PAM : {{ pam_backup.stdout }}" + + - name: Déployer le profil unix (retire nullok, garde use_authtok) + ansible.builtin.template: + src: ../templates/pam-configs-unix.j2 + dest: /usr/share/pam-configs/unix + owner: root + group: root + mode: "0644" + register: pam_unix_profile + + - name: Déployer le profil faillock (authfail) + ansible.builtin.template: + src: ../templates/pam-configs-faillock.j2 + dest: /usr/share/pam-configs/faillock + owner: root + group: root + mode: "0644" + register: pam_faillock_profile + + - name: Déployer le profil faillock_notify (preauth + account) + ansible.builtin.template: + src: ../templates/pam-configs-faillock-notify.j2 + dest: /usr/share/pam-configs/faillock_notify + owner: root + group: root + mode: "0644" + register: pam_faillock_notify_profile + + - name: Déployer le profil pwquality + ansible.builtin.template: + src: ../templates/pam-configs-pwquality.j2 + dest: /usr/share/pam-configs/pwquality + owner: root + group: root + mode: "0644" + register: pam_pwquality_profile + + - name: Déployer le profil pwhistory + ansible.builtin.template: + src: ../templates/pam-configs-pwhistory.j2 + dest: /usr/share/pam-configs/pwhistory + owner: root + group: root + mode: "0644" + register: pam_pwhistory_profile + + - name: Déployer /etc/security/faillock.conf + ansible.builtin.template: + src: ../templates/faillock.conf.j2 + dest: /etc/security/faillock.conf + owner: root + group: root + mode: "0644" + + - name: Déployer /etc/security/pwquality.conf + ansible.builtin.template: + src: ../templates/pwquality.conf.j2 + dest: /etc/security/pwquality.conf + owner: root + group: root + mode: "0644" + + - name: Activer les profils PAM (regénère common-auth/common-account/common-password) + ansible.builtin.command: + cmd: pam-auth-update --enable faillock --enable faillock_notify --enable pwquality --enable pwhistory + when: > + pam_unix_profile.changed or pam_faillock_profile.changed or + pam_faillock_notify_profile.changed or pam_pwquality_profile.changed or + pam_pwhistory_profile.changed + changed_when: true + + - name: Configurer PASS_MAX_DAYS dans login.defs (nouveaux comptes uniquement) + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_MAX_DAYS' + line: "PASS_MAX_DAYS\t365" + + - name: Configurer PASS_MIN_DAYS dans login.defs + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_MIN_DAYS' + line: "PASS_MIN_DAYS\t1" + + - name: Configurer PASS_WARN_AGE dans login.defs + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_WARN_AGE' + line: "PASS_WARN_AGE\t7" + + - name: Verrouillage par défaut des comptes inactifs (nouveaux comptes) + ansible.builtin.command: + cmd: useradd -D -f 30 + changed_when: true + + # --- Vérification critique : la stack PAM fonctionne-t-elle toujours ? --- + # On invalide le cache sudo existant pour forcer une vraie ré-authentification + # PAM, sinon un sudo déjà "chaud" masquerait une stack cassée pendant 15 min. + - name: Invalider le cache sudo pour forcer une vraie revalidation PAM + ansible.builtin.command: + cmd: sudo -K + become: false + changed_when: false + + - name: Vérifier que sudo fonctionne toujours avec la nouvelle stack PAM + ansible.builtin.command: + cmd: whoami + register: pam_sudo_check + failed_when: pam_sudo_check.stdout != "root" + + - name: Confirmer le succès pour cet host + ansible.builtin.debug: + msg: "✅ PAM durci avec succès sur {{ inventory_hostname }} — sudo toujours fonctionnel." diff --git a/hardening/playbook-ssh-hardenning.yml b/hardening/playbook-ssh-hardenning.yml new file mode 100644 index 0000000..e1578de --- /dev/null +++ b/hardening/playbook-ssh-hardenning.yml @@ -0,0 +1,76 @@ +- name: SSH HARDENNING + hosts: load-balancer + become: true + gather_facts: true + tasks: + - name: Créer le groupe ssh + ansible.builtin.group: + name: ssh + state: present + + - ansible.builtin.user: + name: "{{ user_ssh }}" + shell: /bin/bash + groups: + - sudo + - ssh + append: true + state: present + password: "{{ password_user_ssh }}" + update_password: on_create + create_home: true + home: /home/omnex + no_log: true + + - name: Check si la paire de clé SSH existe + ansible.builtin.stat: + path: "{{ playbook_dir }}/../../.ssh/{{ user_ssh }}" + delegate_to: localhost + become: false + register: ssh_key_check + tags: [key] + + - name: Générer la paire de clés SSH localement + community.crypto.openssh_keypair: + path: "{{ playbook_dir }}/../../.ssh/{{ user_ssh }}" + type: ed25519 + delegate_to: localhost + become: false + when: not ssh_key_check.stat.exists + run_once: true + tags: [key] + + - ansible.posix.authorized_key: + user: "{{ user_ssh }}" + state: present + key: "{{ lookup('file', playbook_dir + '/../../.ssh/' + user_ssh + '.pub') }}" + + - name: Déployer la config sshd durcie + ansible.builtin.template: + src: ../templates/ssh_harden_conf.j2 + dest: /etc/ssh/sshd_config + mode: "0644" + owner: root + group: root + validate: /usr/sbin/sshd -t -f %s + notify: restart ssh + + - name: Delete cloud ssh config if exists + ansible.builtin.file: + path: /etc/ssh/sshd_config.d/60-cloudimg-settings.conf + state: absent + + - name: Déployer la config cloud-init durcie + ansible.builtin.template: + src: ../templates/cloud_harden.conf.j2 + dest: /etc/ssh/sshd_config.d/60-cloudimg-settings.conf + mode: "0644" + owner: root + group: root + notify: restart ssh + + handlers: + - name: restart ssh + ansible.builtin.service: + name: ssh + state: restarted diff --git a/infra/playbook-certbot.yml b/infra/playbook-certbot.yml new file mode 100644 index 0000000..04919f5 --- /dev/null +++ b/infra/playbook-certbot.yml @@ -0,0 +1,127 @@ +--- +- name: Génération du certificat SSL Let's Encrypt + hosts: infra + become: true + gather_facts: false + + tasks: + - name: Installer certbot et le plugin nginx + ansible.builtin.apt: + name: + - certbot + - python3-certbot-nginx + - nginx + state: present + update_cache: yes + + - name: Déployer la config Nginx HTTP simple (ACME challenge) + ansible.builtin.copy: + dest: /etc/nginx/sites-available/certbot + content: | + server { + listen 80; + listen [::]:80; + server_name {{ domain_name }} {{ subdomains | join(' ') }}; + + location /.well-known/acme-challenge/ { + root /var/www/html; + } + + location / { + return 200 'ok'; + add_header Content-Type text/plain; + } + } + + - name: Activer le site certbot + ansible.builtin.file: + src: /etc/nginx/sites-available/certbot + dest: /etc/nginx/sites-enabled/certbot + state: link + force: yes + + - name: Désactiver le site par défaut + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + + - name: Tester la configuration Nginx + ansible.builtin.command: nginx -t + changed_when: false + + - name: Redémarrer Nginx + ansible.builtin.systemd: + name: nginx + state: restarted + enabled: yes + + - name: Ouvrir les ports dans UFW + ansible.builtin.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: + - "22" + - "80" + - "443" + + - name: Vérifier si le certificat existe déjà + ansible.builtin.stat: + path: "/etc/letsencrypt/live/{{ domain_name }}/fullchain.pem" + register: cert_file + tags: [gen] + + - name: Générer le certificat avec Certbot (domaine + sous-domaines) + ansible.builtin.command: > + certbot certonly --nginx + -d {{ domain_name }} + {{ subdomains | map('regex_replace', '^(.*)$', '-d \1') | join(' ') }} + --non-interactive + --agree-tos + --email admin@{{ domain_name }} + when: not cert_file.stat.exists + tags: [gen] + + - name: Vérifier que le certificat a bien été généré + ansible.builtin.stat: + path: "/etc/letsencrypt/live/{{ domain_name }}/fullchain.pem" + register: cert_check + + - name: Afficher le résultat + ansible.builtin.debug: + msg: "{{ 'Certificat généré : /etc/letsencrypt/live/' + domain_name + '/fullchain.pem' if cert_check.stat.exists else 'ERREUR : certificat non trouvé' }}" + + - name: Vérifier le renouvellement automatique + ansible.builtin.command: certbot renew --dry-run + changed_when: false + failed_when: false + when: cert_check.stat.exists + + - name: Créer le dossier docker/certs + ansible.builtin.file: + path: "{{ docker_dir }}/nginx/certs" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0750" + tags: [create] + + - name: Copier fullchain.pem dans docker/certs + ansible.builtin.copy: + src: "/etc/letsencrypt/live/{{ domain_name }}/fullchain.pem" + dest: "{{ docker_dir }}/nginx/certs/fullchain.pem" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0644" + remote_src: yes + tags: [create] + + - name: Copier privkey.pem dans docker/certs + ansible.builtin.copy: + src: "/etc/letsencrypt/live/{{ domain_name }}/privkey.pem" + dest: "{{ docker_dir }}/nginx/certs/privkey.pem" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + remote_src: yes + tags: [create] diff --git a/infra/playbook-clamav.yml b/infra/playbook-clamav.yml new file mode 100644 index 0000000..97e355d --- /dev/null +++ b/infra/playbook-clamav.yml @@ -0,0 +1,106 @@ +--- +- name: Installation et configuration du clamAV + hosts: infra + become: true + gather_facts: true + tasks: + - name: Vérifier si ClamAV existe dans les dépôts + ansible.builtin.command: apt-cache search ^clamav$ + register: clamav_search + changed_when: false + + - name: Afficher résultat + ansible.builtin.debug: + var: clamav_search.stdout + + - name: Installer ClamAV si disponible + ansible.builtin.apt: + name: + - clamav + - clamav-daemon + state: present + update_cache: true + when: clamav_search.stdout != "" + + - name: Démarrer ClamAV + ansible.builtin.service: + name: clamav-daemon + state: started + enabled: true + when: clamav_search.stdout != "" + + - name: Arrêter clamav-freshclam avant la mise à jour + ansible.builtin.service: + name: clamav-freshclam + state: stopped + when: clamav_search.stdout != "" + + - name: Mettre à jour la base de données virale (freshclam) + ansible.builtin.command: freshclam + register: freshclam_result + changed_when: "'Database updated' in freshclam_result.stdout" + failed_when: freshclam_result.rc != 0 + when: clamav_search.stdout != "" + + - name: Redémarrer clamav-freshclam après la mise à jour + ansible.builtin.service: + name: clamav-freshclam + state: started + enabled: true + when: clamav_search.stdout != "" + + - name: Configurer ClamAV - activer la détection PUA + ansible.builtin.lineinfile: + path: /etc/clamav/clamd.conf + regexp: "^#?DetectPUA" + line: "DetectPUA yes" + backup: true + when: clamav_search.stdout != "" + notify: Redémarrer clamav-daemon + + - name: Configurer ClamAV - activer les logs + ansible.builtin.lineinfile: + path: /etc/clamav/clamd.conf + regexp: "^#?LogFile " + line: "LogFile /var/log/clamav/clamav.log" + backup: true + when: clamav_search.stdout != "" + notify: Redémarrer clamav-daemon + + - name: Configurer ClamAV - taille max des fichiers scannés + ansible.builtin.lineinfile: + path: /etc/clamav/clamd.conf + regexp: "^#?MaxFileSize" + line: "MaxFileSize 100M" + backup: true + when: clamav_search.stdout != "" + notify: Redémarrer clamav-daemon + + - name: Planifier un scan quotidien via cron + ansible.builtin.cron: + name: "ClamAV daily scan" + minute: "30" + hour: "2" + job: > + ionice -c 3 nice -n 19 + docker exec gestion-clamav + clamscan -r /home /tmp /app/uploads + --log=/var/log/clamav/daily_scan.log --quiet + user: root + when: clamav_search.stdout != "" + + - name: Afficher résultat final + ansible.builtin.debug: + msg: "ClamAV installé et configuré avec succès" + when: clamav_search.stdout != "" + + - name: Afficher message si ClamAV non disponible + ansible.builtin.debug: + msg: "ClamAV n'est pas disponible dans les dépôts" + when: clamav_search.stdout == "" + + handlers: + - name: Redémarrer clamav-daemon + ansible.builtin.service: + name: clamav-daemon + state: restarted diff --git a/infra/playbook-gitea-runner.yml b/infra/playbook-gitea-runner.yml new file mode 100644 index 0000000..84b4e06 --- /dev/null +++ b/infra/playbook-gitea-runner.yml @@ -0,0 +1,332 @@ +--- +- name: Installation et configuration du Gitea Act Runner + hosts: infra + become: false + gather_facts: true + + vars: + act_runner_version: "0.2.11" + act_runner_binary: /usr/local/bin/act_runner + act_runner_workdir: /opt/gitea-runner + act_runner_config: /opt/gitea-runner/.runner + gitea_instance_url: "https://gitea.uber-stup.club" + runner_name: "{{ inventory_hostname }}-runner" + runner_labels: "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04" + + # ── Utilisateur CI/CD ──────────────────────────────── + ci_cd_user: "ci-cd" + ci_cd_user_shell: "/bin/bash" + ci_cd_user_groups: + - docker + ci_cd_ssh_pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJ4COozE+71IvOhzK7ijeQY5lmoDxYTA0+B0lEAO0tG xor_fakers@AsusXor" # ex: "ssh-ed25519 AAAAC3Nza... ci-cd@gitea" + + tasks: + # ── Verrou dpkg (unattended-upgrades / cloud-init au boot) ── + - name: Attendre la libération du verrou dpkg/apt + become: true + shell: | + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \ + || fuser /var/lib/dpkg/lock >/dev/null 2>&1 \ + || fuser /var/lib/apt/lists/lock >/dev/null 2>&1 \ + || fuser /var/cache/apt/archives/lock >/dev/null 2>&1; do + sleep 5 + done + changed_when: false + args: + executable: /bin/bash + tags: always + + # ── Prérequis ────────────────────────────────────────── + - name: Installer les paquets requis + become: true + apt: + name: + - curl + - ca-certificates + state: present + update_cache: yes + register: apt_prereqs_result + until: apt_prereqs_result is succeeded + retries: 10 + delay: 15 + + # ── Docker (requis pour exécuter les jobs) ───────────── + - name: Vérifier si Docker est installé + become: true + command: docker --version + register: docker_check + changed_when: false + failed_when: false + + - name: Installer Docker si absent + become: true + block: + - name: Ajouter la clé GPG Docker + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + + - name: Ajouter le dépôt Docker + apt_repository: + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + filename: docker + + - name: Installer Docker + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + state: present + update_cache: yes + register: apt_docker_result + until: apt_docker_result is succeeded + retries: 10 + delay: 15 + + - name: Démarrer et activer Docker + systemd: + name: docker + state: started + enabled: yes + when: docker_check.rc != 0 + + # ── Utilisateur dédié CI/CD ───────────────────────────── + - name: Vérifier que ci_cd_ssh_pubkey est fourni + fail: + msg: "La variable 'ci_cd_ssh_pubkey' est requise. Relancez avec : -e ci_cd_ssh_pubkey='ssh-ed25519 AAAA...'" + when: ci_cd_ssh_pubkey is not defined or ci_cd_ssh_pubkey == "" + tags: ["ci-cd-user"] + + - name: Créer l'utilisateur CI/CD + become: true + user: + name: "{{ ci_cd_user }}" + shell: "{{ ci_cd_user_shell }}" + groups: "{{ ci_cd_user_groups }}" + append: true + create_home: true + state: present + tags: ["ci-cd-user"] + + - name: Créer le répertoire .ssh de l'utilisateur CI/CD + become: true + file: + path: "/home/{{ ci_cd_user }}/.ssh" + state: directory + owner: "{{ ci_cd_user }}" + group: "{{ ci_cd_user }}" + mode: "0700" + tags: ["ci-cd-user"] + + - name: Déployer la clé publique SSH de l'utilisateur CI/CD + become: true + authorized_key: + user: "{{ ci_cd_user }}" + key: "{{ ci_cd_ssh_pubkey }}" + state: present + exclusive: false + tags: ["ci-cd-user"] + + - name: Autoriser l'utilisateur CI/CD à utiliser Docker sans mot de passe (sudo ciblé, optionnel) + become: true + copy: + dest: "/etc/sudoers.d/{{ ci_cd_user }}-docker" + content: "{{ ci_cd_user }} ALL=(ALL) NOPASSWD: /usr/bin/docker\n" + mode: "0440" + validate: "visudo -cf %s" + tags: ["ci-cd-user"] + + # ── Téléchargement act_runner ────────────────────────── + - name: Vérifier si act_runner est déjà installé + become: true + stat: + path: "{{ act_runner_binary }}" + register: runner_bin + tags: ["runner", "register-runner"] + + - name: Récupérer la version installée + become: true + command: "{{ act_runner_binary }} --version" + register: installed_version + changed_when: false + failed_when: false + when: runner_bin.stat.exists + tags: ["runner", "register-runner"] + + - name: Télécharger act_runner v{{ act_runner_version }} + become: true + get_url: + url: "https://dl.gitea.com/act_runner/{{ act_runner_version }}/act_runner-{{ act_runner_version }}-linux-amd64" + dest: "{{ act_runner_binary }}" + mode: "0755" + force: yes + tags: ["runner", "register-runner"] + when: > + not (runner_bin.stat.exists | default(false)) or + (installed_version.stdout is defined and act_runner_version not in installed_version.stdout) + # ── Répertoire de travail ────────────────────────────── + - name: Créer le répertoire de travail + become: true + file: + path: "{{ act_runner_workdir }}" + state: directory + mode: "0750" + tags: ["register-runner"] + + # ── Enregistrement du runner ─────────────────────────── + - name: Vérifier que gitea_runner_token est fourni + become: true + fail: + msg: "La variable 'gitea_runner_token' est requise. Relancez avec : -e gitea_runner_token=" + when: gitea_runner_token is not defined or gitea_runner_token == "" + tags: ["register-runner"] + + - name: Vérifier si le runner est déjà enregistré + become: true + stat: + path: "{{ act_runner_config }}" + register: runner_config + tags: ["register-runner"] + + - name: Arrêter le service avant re-enregistrement + become: true + systemd: + name: gitea-runner + state: stopped + failed_when: false + when: gitea_runner_token is defined and runner_config.stat.exists + tags: ["register-runner"] + + - name: Supprimer l'ancienne configuration du runner + become: true + file: + path: "{{ act_runner_config }}" + state: absent + when: gitea_runner_token is defined and runner_config.stat.exists + tags: ["register-runner"] + + - name: Enregistrer le runner auprès de Gitea + become: true + command: > + {{ act_runner_binary }} register + --instance {{ gitea_instance_url }} + --token {{ gitea_runner_token }} + --name {{ runner_name }} + --labels {{ runner_labels }} + --no-interactive + args: + chdir: "{{ act_runner_workdir }}" + no_log: true + tags: ["register-runner"] + + # ── Service systemd ──────────────────────────────────── + - name: Créer le service systemd gitea-runner + become: true + copy: + dest: /etc/systemd/system/gitea-runner.service + mode: "0644" + content: | + [Unit] + Description=Gitea Act Runner + After=network.target docker.service + Requires=docker.service + + [Service] + ExecStart={{ act_runner_binary }} daemon + WorkingDirectory={{ act_runner_workdir }} + Restart=always + RestartSec=5 + User=root + StandardOutput=journal + StandardError=journal + + [Install] + WantedBy=multi-user.target + notify: Recharger systemd + tags: ["register-runner"] + + - name: Activer et démarrer le service gitea-runner + become: true + systemd: + name: gitea-runner + state: started + enabled: yes + daemon_reload: yes + tags: ["register-runner"] + + # ── Vérification ─────────────────────────────────────── + - name: Vérifier le statut du runner + become: true + command: systemctl status gitea-runner + register: runner_status + changed_when: false + failed_when: false + tags: ["register-runner"] + + - name: Afficher le statut + become: true + debug: + msg: "{{ runner_status.stdout_lines }}" + tags: ["register-runner"] + + - name: Check if swap is already active + become: true + ansible.builtin.command: swapon --show + register: swap_status + changed_when: false + tags: ["register-runner"] + + - name: Create swapfile + become: true + ansible.builtin.command: "fallocate -l {{ swap_size }} {{ swap_file }}" + args: + creates: "{{ swap_file }}" + tags: ["register-runner"] + when: swap_status.stdout == "" + + - name: Set swapfile permissions + become: true + ansible.builtin.file: + path: "{{ swap_file }}" + mode: "0600" + owner: root + group: root + tags: ["register-runner"] + + - name: Format swap + become: true + ansible.builtin.command: mkswap {{ swap_file }} + tags: ["register-runner"] + when: swap_status.stdout == "" + + - name: Enable swap + become: true + ansible.builtin.command: swapon {{ swap_file }} + tags: ["register-runner"] + when: swap_status.stdout == "" + + - name: Ensure swap in fstab + become: true + ansible.builtin.lineinfile: + path: /etc/fstab + line: "{{ swap_file }} none swap sw 0 0" + state: present + tags: ["register-runner"] + + - name: Set swappiness + become: true + ansible.builtin.sysctl: + name: vm.swappiness + value: "{{ swap_swappiness }}" + state: present + reload: yes + tags: ["register-runner"] + + handlers: + - name: Recharger systemd + become: true + systemd: + daemon_reload: yes diff --git a/infra/playbook-s3-backup.yml b/infra/playbook-s3-backup.yml new file mode 100644 index 0000000..6e863bf --- /dev/null +++ b/infra/playbook-s3-backup.yml @@ -0,0 +1,146 @@ +--- +- name: S3 backup pour la db de prod + hosts: infra + become: true + gather_facts: true + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - acl + state: present + tags: docker + + - name: Create directory for Docker GPG key + file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + tags: docker + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + tags: docker + + - name: Get system architecture + command: dpkg --print-architecture + register: system_arch + changed_when: false + + - name: Get Ubuntu codename + command: lsb_release -cs + register: ubuntu_codename + changed_when: false + + - name: Add Docker repository + apt_repository: + repo: "deb [arch={{ system_arch.stdout }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename.stdout }} stable" + state: present + filename: docker + tags: docker + + - name: Update apt cache after adding repo + apt: + update_cache: yes + tags: docker + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + tags: docker + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + tags: docker + + - name: Create docker group + group: + name: "{{ docker_group }}" + state: present + tags: docker + + - name: Create docker user + user: + name: "{{ docker_user }}" + group: "{{ docker_group }}" + groups: docker + append: yes + shell: /bin/bash + create_home: yes + state: present + tags: docker + + - name: Add ansible user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + when: ansible_user is defined + tags: docker + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: docker + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + changed_when: false + tags: docker + + - name: Display versions + debug: + msg: + - "{{ docker_version.stdout }}" + - "{{ compose_version.stdout }}" + tags: docker + + - name: Copy S3 project + ansible.builtin.copy: + src: ../monitoring/s3-backup/ + dest: "{{ docker_dir }}/" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Ensure parent directory has correct permissions + ansible.builtin.file: + path: "{{ docker_dir }}" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Set correct permissions for Docker files + ansible.builtin.file: + path: "{{ docker_dir }}" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + recurse: yes + tags: project diff --git a/infra/playbook-s3.yml b/infra/playbook-s3.yml new file mode 100644 index 0000000..b128613 --- /dev/null +++ b/infra/playbook-s3.yml @@ -0,0 +1,146 @@ +--- +- name: Déploiement OTA & S3 pour les build + hosts: infra + become: true + gather_facts: true + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - acl + state: present + tags: docker + + - name: Create directory for Docker GPG key + file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + tags: docker + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + tags: docker + + - name: Get system architecture + command: dpkg --print-architecture + register: system_arch + changed_when: false + + - name: Get Ubuntu codename + command: lsb_release -cs + register: ubuntu_codename + changed_when: false + + - name: Add Docker repository + apt_repository: + repo: "deb [arch={{ system_arch.stdout }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename.stdout }} stable" + state: present + filename: docker + tags: docker + + - name: Update apt cache after adding repo + apt: + update_cache: yes + tags: docker + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + tags: docker + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + tags: docker + + - name: Create docker group + group: + name: "{{ docker_group }}" + state: present + tags: docker + + - name: Create docker user + user: + name: "{{ docker_user }}" + group: "{{ docker_group }}" + groups: docker + append: yes + shell: /bin/bash + create_home: yes + state: present + tags: docker + + - name: Add ansible user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + when: ansible_user is defined + tags: docker + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: docker + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + changed_when: false + tags: docker + + - name: Display versions + debug: + msg: + - "{{ docker_version.stdout }}" + - "{{ compose_version.stdout }}" + tags: docker + + - name: Copy S3 project + ansible.builtin.copy: + src: ../monitoring/s3/ + dest: "{{ docker_dir }}/" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Ensure parent directory has correct permissions + ansible.builtin.file: + path: "{{ docker_dir }}" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Set correct permissions for Docker files + ansible.builtin.file: + path: "{{ docker_dir }}" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + recurse: yes + tags: project diff --git a/infra/playbook-security.yml b/infra/playbook-security.yml new file mode 100644 index 0000000..a50118b --- /dev/null +++ b/infra/playbook-security.yml @@ -0,0 +1,155 @@ +--- +- name: Déploiement des solution de monitoring et de sécurité + hosts: infra + become: true + gather_facts: true + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - acl + state: present + tags: docker + + - name: Create directory for Docker GPG key + file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + tags: docker + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + tags: docker + + - name: Get system architecture + command: dpkg --print-architecture + register: system_arch + changed_when: false + + - name: Get Ubuntu codename + command: lsb_release -cs + register: ubuntu_codename + changed_when: false + + - name: Add Docker repository + apt_repository: + repo: "deb [arch={{ system_arch.stdout }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename.stdout }} stable" + state: present + filename: docker + tags: docker + + - name: Update apt cache after adding repo + apt: + update_cache: yes + tags: docker + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + tags: docker + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + tags: docker + + - name: Create docker group + group: + name: "{{ docker_group }}" + state: present + tags: docker + + - name: Create docker user + user: + name: "{{ docker_user }}" + group: "{{ docker_group }}" + groups: docker + append: yes + shell: /bin/bash + create_home: yes + state: present + tags: docker + + - name: Add ansible user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + when: ansible_user is defined + tags: docker + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: docker + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + changed_when: false + tags: docker + + - name: Display versions + debug: + msg: + - "{{ docker_version.stdout }}" + - "{{ compose_version.stdout }}" + tags: docker + + - name: Copy Docker project + ansible.builtin.copy: + src: ../monitoring/security/ + dest: "{{ docker_dir }}/" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Ensure parent directory has correct permissions + ansible.builtin.file: + path: "{{ docker_dir }}" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Set correct permissions for Docker files + ansible.builtin.file: + path: "{{ docker_dir }}" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + recurse: yes + tags: project + + - name: Set permissions for .env file + ansible.builtin.file: + path: "{{ docker_dir }}/.env" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + tags: project + ignore_errors: yes diff --git a/infra/playbook-unattended-upgrades.yml b/infra/playbook-unattended-upgrades.yml new file mode 100644 index 0000000..e4f36a1 --- /dev/null +++ b/infra/playbook-unattended-upgrades.yml @@ -0,0 +1,77 @@ +--- +- name: Mises à jour de sécurité automatiques (unattended-upgrades) + hosts: load-balancer + become: true + + tasks: + - name: Installer unattended-upgrades et jq (notification Discord) + ansible.builtin.apt: + name: + - unattended-upgrades + - jq + state: present + update_cache: true + + - name: Déployer la config des origines/exclusions (50unattended-upgrades) + ansible.builtin.template: + src: ../templates/unattended-upgrades-50.j2 + dest: /etc/apt/apt.conf.d/50unattended-upgrades + owner: root + group: root + mode: "0644" + + - name: Déployer la config d'activation périodique (20auto-upgrades) + ansible.builtin.template: + src: ../templates/unattended-upgrades-20.j2 + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: "0644" + + - name: Déployer le script de notification Discord + ansible.builtin.template: + src: ../templates/unattended-upgrades-notify.sh.j2 + dest: /usr/local/bin/unattended-upgrades-notify.sh + owner: root + group: root + mode: "0750" + + - name: Déployer le service systemd de notification + ansible.builtin.template: + src: ../templates/unattended-upgrades-notify.service.j2 + dest: /etc/systemd/system/unattended-upgrades-notify.service + owner: root + group: root + mode: "0644" + notify: Recharger systemd + + - name: Déployer le timer systemd de notification + ansible.builtin.template: + src: ../templates/unattended-upgrades-notify.timer.j2 + dest: /etc/systemd/system/unattended-upgrades-notify.timer + owner: root + group: root + mode: "0644" + notify: Recharger systemd + + - name: Activer et démarrer le timer de notification + ansible.builtin.systemd: + name: unattended-upgrades-notify.timer + enabled: true + state: started + daemon_reload: true + + - name: Vérifier la config avec un dry-run + ansible.builtin.command: unattended-upgrade --dry-run --debug + register: dry_run_result + changed_when: false + failed_when: false + + - name: Afficher le résultat du dry-run + ansible.builtin.debug: + var: dry_run_result.stdout_lines + + handlers: + - name: Recharger systemd + ansible.builtin.systemd: + daemon_reload: true diff --git a/infra/playbook-wireguard-vpn.yml b/infra/playbook-wireguard-vpn.yml new file mode 100644 index 0000000..410f970 --- /dev/null +++ b/infra/playbook-wireguard-vpn.yml @@ -0,0 +1,132 @@ +--- +- name: Configure WireGuard VPN Client + hosts: load-balancer + become: true + vars: + vpn_ip: "10.0.0.12" + vpn_private_key: "+E1pcelkLIr0x6FcOofnAWAFr6g5qHu8QHpG1sY6mG4=" + vpn_server_pubkey: "VQa6g1foaXhJgYWbwyJ9R/EAmmXg0nOnhPhbIIPLlmg=" + vpn_server_ip: "131.123.42.158" + vpn_server_port: "51820" + vpn_network: "10.0.0.0/24" + vpn_private_ip: "10.0.0.1" + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install WireGuard and tools + apt: + name: + - wireguard + - wireguard-tools + state: present + + - name: Create WireGuard config directory + file: + path: /etc/wireguard + state: directory + mode: '0700' + + - name: Create WireGuard config + copy: + content: | + [Interface] + Address = {{ vpn_ip }}/32 + ListenPort = 51821 + PrivateKey = {{ vpn_private_key }} + + [Peer] + PublicKey = {{ vpn_server_pubkey }} + Endpoint = {{ vpn_server_ip }}:{{ vpn_server_port }} + AllowedIPs = {{ vpn_network }} + PersistentKeepalive = 25 + dest: /etc/wireguard/wg0.conf + mode: '0600' + register: wg_config + + - name: Enable WireGuard service + systemd: + name: wg-quick@wg0 + enabled: yes + state: started + + - name: Redémarrer WireGuard si la config a changé (state:started ne recharge pas seul) + systemd: + name: wg-quick@wg0 + state: restarted + when: wg_config.changed + +- name: Enregistrer le pair load-balancer sur le serveur VPN + hosts: vpn-uber + become: true + vars: + vpn_ip: "10.0.0.12" + vpn_private_key: "+E1pcelkLIr0x6FcOofnAWAFr6g5qHu8QHpG1sY6mG4=" + + tasks: + - name: Calculer la clé publique du client à partir de sa clé privée + command: wg pubkey + args: + stdin: "{{ vpn_private_key }}" + delegate_to: localhost + become: false + register: client_pubkey + changed_when: false + no_log: true + tags: [vpn] + + - name: Ajouter le pair dans wg0.conf + ansible.builtin.blockinfile: + path: /etc/wireguard/wg0.conf + marker: "# {mark} PEER load-balancer" + insertafter: EOF + block: | + [Peer] + PublicKey = {{ client_pubkey.stdout }} + AllowedIPs = {{ vpn_ip }}/32 + register: peer_added + tags: [vpn] + + + - name: Appliquer la config sans couper les autres pairs + ansible.builtin.shell: wg syncconf wg0 <(wg-quick strip wg0) + args: + executable: /bin/bash + when: peer_added.changed + tags: [vpn] + +- name: Vérifier la connectivité VPN du load-balancer + hosts: load-balancer + become: true + vars: + vpn_ip: "10.0.0.12" + vpn_private_ip: "10.0.0.1" + + tasks: + - name: Wait for VPN connection to establish + pause: + seconds: 3 + + - name: Check VPN connection + command: ip addr show wg0 + register: wg_status + changed_when: false + + - name: Test VPN connectivity + command: ping -c 1 "{{ vpn_private_ip }}" + register: ping_result + ignore_errors: yes + + - name: Display VPN status + debug: + msg: | + VPN Configuration Complete! + IP Address: {{ vpn_ip }} + WireGuard Status: + {{ wg_status.stdout }} + + Ping result ({{ 'SUCCESS' if ping_result.rc == 0 else 'FAILED' }}): + {{ ping_result.stdout | default('Connection test deferred') }} diff --git a/inventory/group_vars/all/legacy_vars.yml b/inventory/group_vars/all/legacy_vars.yml new file mode 100644 index 0000000..24bba23 --- /dev/null +++ b/inventory/group_vars/all/legacy_vars.yml @@ -0,0 +1,15 @@ +# ============================================================ +# Variables de l'ancien déploiement natif (hosts demo-uber / uber-stup-web, +# playbooks playbook-backend.yml / playbook-frontend.yml / playbook-file.yml). +# Ces playbooks n'existent plus dans infra/, prod/ ou pre-prod/, et aucune de +# ces variables n'est référencée par un playbook actif. +# Conservées pour ne rien casser silencieusement — à confirmer puis supprimer. +# ============================================================ +directory: "/home/ubuntu" +backend_dir: "/home/ubuntu/backend" +backend_binary: "/home/ubuntu/backend/main" +uploads_dir: "/home/ubuntu/backend/uploads" +frontend_port: 5173 +user_web: "www-data" +discord_webhook_url: "REMPLACER_PAR_TON_WEBHOOK_DISCORD" +user_owner: "root" diff --git a/inventory/group_vars/all/vars.yml b/inventory/group_vars/all/vars.yml new file mode 100644 index 0000000..8938313 --- /dev/null +++ b/inventory/group_vars/all/vars.yml @@ -0,0 +1,14 @@ +# Docker — utilisé par tous les playbooks qui installent Docker (prod, pre-prod, infra) +docker_user: "ubuntu" +docker_group: "docker" +docker_dir: "/home/ubuntu/docker" + +# Notification Discord infra (unattended-upgrades, etc.) — infra/playbook-unattended-upgrades.yml +# Laisser vide désactive silencieusement l'envoi (voir unattended-upgrades-notify.sh.j2). +discord_infra_webhook_url: "https://discord.com/api/webhooks/1526937190410621032/EUxNm2AMFByTPa9sWmwiX1fDpl41eEKO1ssj9gJxCLkqAwQ4laRSDXNxMeToseLQROAc" + +# CIS 3.3.1 (ip forwarding) — hardening/playbook-kernel-sysctl-hardening.yml +# Par défaut laissé actif (false = ne pas désactiver) : la plupart des hosts font tourner Docker +# (NAT des ports publiés) ou servent de relais VPN. Surchargé à true uniquement pour les hosts +# sans aucun rôle de forwarding (voir group_vars/load-balancer). +ip_forwarding_disabled: false diff --git a/inventory/group_vars/bdd-redis-prod/vars.yml b/inventory/group_vars/bdd-redis-prod/vars.yml new file mode 100644 index 0000000..c3a90e7 --- /dev/null +++ b/inventory/group_vars/bdd-redis-prod/vars.yml @@ -0,0 +1,40 @@ +# PostgreSQL +pg_version: "16" +db_user: admin_gestion_commande_db +db_name: gestion_commande_db +db_password: "1SWDxH20rV7K2Uc2PNlwCaCxfVZEtKomF0CK9OMh" +db_host: "localhost" +db_port: 5432 + +# Redis +redis_bind: "127.0.0.1" +redis_port: 6379 +redis_maxmemory: "1024mb" +redis_maxmemory_policy: "allkeys-lru" +redis_password: "k6UYX9RtuXJVV1HUeefbSukMcSwjvVgRsh2qJGPh" + +# Nginx / backend +nginx_enable_firewall: true +backend_local_port: 8080 +backend_port: 80 +nginx_proxy_timeout: 60 +nginx_max_body_size: "10M" + +# Sauvegarde Postgres (Plakar) — prod/playbook-backup.yml +plakar_version: "1.1.4" +plakar_go_version: "1.24.0" +plakar_backup_user: plakar-backup +plakar_home_dir: /var/lib/plakar-backup +plakar_dump_dir: /var/lib/plakar-backup/dumps +plakar_config_dir: /etc/plakar-backup +plakar_passphrase_file: /etc/plakar-backup/passphrase + +# Dépôt Plakar (Kloset) distant — stocké sur MinIO via S3 +plakar_store_name: minio-backup +plakar_s3_endpoint: "10.0.0.4:8080" +plakar_s3_bucket: backup-db-prod-mln +plakar_s3_prefix: postgresql +plakar_s3_access_key: admin +plakar_s3_secret_key: "admin@12345" +plakar_s3_use_tls: true +plakar_email: "free6nk33@gmail.com" diff --git a/inventory/group_vars/infra-runner/vars.yml b/inventory/group_vars/infra-runner/vars.yml new file mode 100644 index 0000000..d805238 --- /dev/null +++ b/inventory/group_vars/infra-runner/vars.yml @@ -0,0 +1,4 @@ +# Utilisé par infra/playbook-gitea-runner.yml (configuration du swap sur le runner CI) +swap_size: 4G +swap_file: /swapfile +swap_swappiness: 10 diff --git a/inventory/group_vars/infra/vars.yml b/inventory/group_vars/infra/vars.yml new file mode 100644 index 0000000..54fbabc --- /dev/null +++ b/inventory/group_vars/infra/vars.yml @@ -0,0 +1,4 @@ +# Utilisé par infra/playbook-harden-ssh.yml +user_ssh: "omnex" +group_ssh: "ssh" +password_user_ssh: "$6$w6GBkTHgj8K84P7o$pToS4pRMGLKwfpRfnz6i.WpO72Lt5DLtFQWZJsKL2dKvQrdKdi2q8xRuWqq/W3ybFNLQapvAqN33HexVjajGw0" #aiQgIAcCVIy1E33ReRssfm414dm diff --git a/inventory/group_vars/load-balancer/vars.yml b/inventory/group_vars/load-balancer/vars.yml new file mode 100644 index 0000000..a09b0f4 --- /dev/null +++ b/inventory/group_vars/load-balancer/vars.yml @@ -0,0 +1,9 @@ +# Utilisé par infra/playbook-harden-ssh.yml +user_ssh: "omnex" +group_ssh: "ssh" +password_user_ssh: "$6$w6GBkTHgj8K84P7o$pToS4pRMGLKwfpRfnz6i.WpO72Lt5DLtFQWZJsKL2dKvQrdKdi2q8xRuWqq/W3ybFNLQapvAqN33HexVjajGw0" #aiQgIAcCVIy1E33ReRssfm414dm + +# CIS 3.3.1 — hardening/playbook-kernel-sysctl-hardening.yml +# load-balancer est un reverse proxy applicatif pur (pas de Docker, pas de relais VPN) : +# aucun besoin de forwarding kernel, peut être désactivé sans risque. +ip_forwarding_disabled: true diff --git a/inventory/group_vars/pre-prod/vars.yml b/inventory/group_vars/pre-prod/vars.yml new file mode 100644 index 0000000..bc3ff1f --- /dev/null +++ b/inventory/group_vars/pre-prod/vars.yml @@ -0,0 +1,3 @@ +# Dossier source du projet Docker Compose copié sur l'hôte (pre-prod/playbook-pre-prod.yml) +# NOTE : ce dossier n'existe pas encore dans le repo (voir remarque précédente sur docker-pre-prod/ manquant) +compose_project_dir: "../docker-pre-prod/" diff --git a/inventory/group_vars/prod/vars.yml b/inventory/group_vars/prod/vars.yml new file mode 100644 index 0000000..0f3c48a --- /dev/null +++ b/inventory/group_vars/prod/vars.yml @@ -0,0 +1,7 @@ +# Dossier source du projet Docker Compose copié sur l'hôte (prod/playbook-prod.yml) +compose_project_dir: "../docker-prod/" + +# Utilisé uniquement par prod/playbook-s3-img.yml (les autres playbooks du groupe utilisent ansible_user) +user_deploy: "ubuntu" +docker_group: "docker" +docker_dir: "/home/ubuntu/docker/s3-img" diff --git a/inventory/group_vars/replica-prod/vars.yml b/inventory/group_vars/replica-prod/vars.yml new file mode 100644 index 0000000..dd6ec73 --- /dev/null +++ b/inventory/group_vars/replica-prod/vars.yml @@ -0,0 +1,4 @@ +# Même stack que prod (prod/playbook-prod.yml cible désormais ce groupe) +compose_project_dir: "../../docker-prod/" +docker_group: "docker" +user_deploy: "ubuntu" diff --git a/inventory/host.ini b/inventory/host.ini new file mode 100644 index 0000000..3afdd9c --- /dev/null +++ b/inventory/host.ini @@ -0,0 +1,36 @@ +[prod] +prod-uber ansible_host=131.123.39.119 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.6 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm + +[pre-prod] +pre-prod-uber ansible_host=131.123.39.112 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.7 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm + +[bdd-redis-prod] +bdd-redis-prod ansible_host=132.243.162.62 ansible_user=root ansible_ssh_private_key_file=../.ssh/key_bdd_redis vpn_ip=10.0.0.5 + +[replica-prod] +replica-prod-uber ansible_host=132.243.162.161 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.9 + +[infra-monitoring-security] +monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.2 + +[infra-runner] +runner-uber ansible_host=185.103.164.200 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex + +[infra-utils] +s3-uber ansible_host=80.96.58.164 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm + +[vpn-uber] +vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.1 + +[load-balancer] +load-balancer ansible_host=93.185.166.221 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex + +[infra] +monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.2 +vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.1 +backup-mln ansible_host=85.121.176.241 ansible_user=root ansible_ssh_private_key_file=../.ssh/key_backup vpn_ip=10.0.0.4 + +[infra:children] +infra-monitoring-security +infra-runner +infra-utils diff --git a/inventory/host_vars/monitoring-uber.yml b/inventory/host_vars/monitoring-uber.yml new file mode 100644 index 0000000..9752af5 --- /dev/null +++ b/inventory/host_vars/monitoring-uber.yml @@ -0,0 +1,9 @@ +# Utilisé par infra/playbook-certbot.yml (certificat unique couvrant le domaine + sous-domaines) +# Valeurs héritées de l'ancien group_vars/all/vars.yml ("demo-uber.xyz") — ne correspondent +# pas au domaine réel du VPS monitoring documenté dans INFRA.md ("uber-stup.club"). +# À CONFIRMER avant tout run de ce playbook, sous peine de générer un certificat pour le mauvais domaine. +domain_name: "demo-uber.xyz" +subdomains: + - wazuh.demo-uber.xyz + - dozzle.demo-uber.xyz + - beszel.demo-uber.xyz diff --git a/pre-prod/playbook-pre-prod.yml b/pre-prod/playbook-pre-prod.yml new file mode 100644 index 0000000..15e2e0a --- /dev/null +++ b/pre-prod/playbook-pre-prod.yml @@ -0,0 +1,164 @@ +--- +- name: Déploiement de la pre-prod + hosts: pre-prod + become: true + gather_facts: true + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - acl + state: present + tags: docker + + - name: Create directory for Docker GPG key + file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + tags: docker + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + tags: docker + + - name: Get system architecture + command: dpkg --print-architecture + register: system_arch + changed_when: false + + - name: Get Ubuntu codename + command: lsb_release -cs + register: ubuntu_codename + changed_when: false + + - name: Add Docker repository + apt_repository: + repo: "deb [arch={{ system_arch.stdout }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename.stdout }} stable" + state: present + filename: docker + tags: docker + + - name: Update apt cache after adding repo + apt: + update_cache: yes + tags: docker + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + tags: docker + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + tags: docker + + - name: Create docker group + group: + name: "{{ docker_group }}" + state: present + tags: docker + + - name: Create docker user + user: + name: "{{ docker_user }}" + group: "{{ docker_group }}" + groups: docker + append: yes + shell: /bin/bash + create_home: yes + state: present + tags: docker + + - name: Add ansible user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + when: ansible_user is defined + tags: docker + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: docker + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + changed_when: false + tags: docker + + - name: Display versions + debug: + msg: + - "{{ docker_version.stdout }}" + - "{{ compose_version.stdout }}" + tags: docker + + - name: Copy Docker project + ansible.builtin.copy: + src: "{{ compose_project_dir }}" + dest: "{{ docker_dir }}/" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Ensure parent directory has correct permissions + ansible.builtin.file: + path: "{{ docker_dir }}" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Set correct permissions for Docker files + ansible.builtin.file: + path: "{{ docker_dir }}" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + recurse: yes + tags: project + + - name: Set permissions for .env file + ansible.builtin.file: + path: "{{ docker_dir }}/.env.example" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + tags: project + ignore_errors: yes + + - name: Set permissions for lbtelegram.env file + ansible.builtin.file: + path: "{{ docker_dir }}/lbtelegram.env" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + tags: project + ignore_errors: yes diff --git a/prod/playbook-bdd-redis.yml b/prod/playbook-bdd-redis.yml new file mode 100644 index 0000000..81966b1 --- /dev/null +++ b/prod/playbook-bdd-redis.yml @@ -0,0 +1,308 @@ +--- +# ============================================ +# PostgreSQL Installation (vm-postgres uniquement) +# ============================================ +- name: Installation et configuration de PostgreSQL + hosts: bdd-redis-prod + become: true + gather_facts: true + + tasks: + # ============================================================ + # Dépendances requises + # ============================================================ + - name: Installer les dépendances pour le dépôt APT + ansible.builtin.apt: + name: + - gnupg2 + - curl + - ca-certificates + - lsb-release + - python3-psycopg2 + - acl + state: present + update_cache: yes + + # ============================================================ + # Ajout du dépôt officiel PostgreSQL + # ============================================================ + - name: Ajouter la clé GPG du dépôt PostgreSQL + ansible.builtin.apt_key: + url: https://www.postgresql.org/media/keys/ACCC4CF8.asc + state: present + + - name: Ajouter le dépôt PostgreSQL 16 + ansible.builtin.apt_repository: + repo: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main" + state: present + filename: pgdg + + # ============================================================ + # Installation PostgreSQL 16 + # ============================================================ + - name: Installer PostgreSQL 16 + ansible.builtin.apt: + name: + - "postgresql-{{ pg_version }}" + - "postgresql-client-{{ pg_version }}" + - "postgresql-contrib-{{ pg_version }}" + state: present + update_cache: yes + + - name: Démarrer et activer PostgreSQL + ansible.builtin.systemd: + name: postgresql + state: started + enabled: yes + + # ============================================================ + # Configuration PostgreSQL + # ============================================================ + - name: Configurer l'écoute sur localhost + ansible.builtin.lineinfile: + path: "/etc/postgresql/{{ pg_version }}/main/postgresql.conf" + regexp: "^#?listen_addresses" + line: "listen_addresses = 'localhost'" + notify: Restart postgresql + + - name: Configurer le port + ansible.builtin.lineinfile: + path: "/etc/postgresql/{{ pg_version }}/main/postgresql.conf" + regexp: "^#?port" + line: "port = {{ db_port }}" + notify: Restart postgresql + + # ============================================================ + # Locale requise + # ============================================================ + - name: Générer la locale fr_FR.UTF-8 + ansible.builtin.command: locale-gen fr_FR.UTF-8 + register: locale_gen + changed_when: "'Generation complete' in locale_gen.stdout" + + - name: Mettre à jour les locales + ansible.builtin.command: update-locale + when: locale_gen.changed + + # ============================================================ + # Création de la base de données et configuration utilisateur + # ============================================================ + - name: Définir le mot de passe de l'utilisateur postgres + become_user: postgres + ansible.builtin.postgresql_user: + name: "{{ db_user }}" + password: "{{ db_password }}" + state: present + + - name: Vérifier si la base de données existe + become_user: postgres + ansible.builtin.command: psql -tAc "SELECT 1 FROM pg_database WHERE datname='{{ db_name }}'" + register: db_exists + changed_when: false + + - name: Créer la base de données avec ICU + become_user: postgres + ansible.builtin.command: > + psql -c "CREATE DATABASE {{ db_name }} + OWNER {{ db_user }} + ENCODING 'UTF8' + LOCALE_PROVIDER icu + ICU_LOCALE 'fr-FR' + TEMPLATE template0" + when: db_exists.stdout != "1" + + - name: Accorder tous les privilèges sur la base + become_user: postgres + ansible.builtin.postgresql_privs: + db: "{{ db_name }}" + role: "{{ db_user }}" + type: database + privs: ALL + state: present + + # ============================================================ + # Authentification pg_hba.conf + # ============================================================ + - name: Configurer l'authentification md5 pour les connexions locales + ansible.builtin.postgresql_pg_hba: + dest: "/etc/postgresql/{{ pg_version }}/main/pg_hba.conf" + contype: host + databases: "{{ db_name }}" + users: "{{ db_user }}" + source: "127.0.0.1/32" + method: md5 + notify: Restart postgresql + + - name: Configurer l'authentification md5 pour IPv6 local + ansible.builtin.postgresql_pg_hba: + dest: "/etc/postgresql/{{ pg_version }}/main/pg_hba.conf" + contype: host + databases: "{{ db_name }}" + users: "{{ db_user }}" + source: "::1/128" + method: md5 + notify: Restart postgresql + + # ============================================================ + # Vérification + # ============================================================ + - name: Vérifier la version de PostgreSQL + become_user: postgres + ansible.builtin.command: psql --version + register: pg_version_check + changed_when: false + + - name: Afficher la version + ansible.builtin.debug: + msg: "{{ pg_version_check.stdout }}" + + - name: Vérifier que la base existe + become_user: postgres + ansible.builtin.command: psql -lqt + register: pg_databases + changed_when: false + + - name: Afficher les bases de données + ansible.builtin.debug: + msg: "{{ pg_databases.stdout_lines }}" + + handlers: + - name: Restart postgresql + ansible.builtin.systemd: + name: postgresql + state: restarted + +# ============================================ +# Redis Installation (vm-redis uniquement) +# ============================================ +- name: Installation et configuration de Redis + hosts: bdd-redis-prod + become: true + gather_facts: true + + tasks: + - name: Installer Redis + apt: + name: + - redis-server + - python3-redis + state: present + update_cache: yes + + - name: Créer le répertoire de backup Redis + file: + path: /var/lib/redis/backup + state: directory + owner: redis + group: redis + mode: "0755" + + - name: Configurer Redis - bind address (accepter connexions réseau) + lineinfile: + path: /etc/redis/redis.conf + regexp: "^bind" + line: "bind 127.0.0.1 {{ vpn_ip }}" + state: present + notify: Redémarrer Redis + + - name: Configurer Redis - port + lineinfile: + path: /etc/redis/redis.conf + regexp: "^port" + line: "port {{ redis_port }}" + state: present + notify: Redémarrer Redis + + - name: Configurer Redis - maxmemory + lineinfile: + path: /etc/redis/redis.conf + regexp: "^# ?maxmemory" + line: "maxmemory {{ redis_maxmemory }}" + state: present + notify: Redémarrer Redis + + - name: Configurer Redis - maxmemory-policy + lineinfile: + path: /etc/redis/redis.conf + regexp: "^# ?maxmemory-policy" + line: "maxmemory-policy {{ redis_maxmemory_policy }}" + state: present + notify: Redémarrer Redis + + - name: Configurer Redis - requirepass + lineinfile: + path: /etc/redis/redis.conf + regexp: "^# ?requirepass" + line: "requirepass {{ redis_password }}" + state: present + notify: Redémarrer Redis + + - name: Activer AOF (Append Only File) pour Redis + lineinfile: + path: /etc/redis/redis.conf + regexp: "^appendonly" + line: "appendonly yes" + state: present + notify: Redémarrer Redis + + - name: Configurer la fréquence de sync AOF + lineinfile: + path: /etc/redis/redis.conf + regexp: "^# ?appendfsync" + line: "appendfsync everysec" + state: present + notify: Redémarrer Redis + + - name: Activer la persistance RDB (snapshots) + blockinfile: + path: /etc/redis/redis.conf + block: | + save 900 1 + save 300 10 + save 60 10000 + marker: "# {mark} ANSIBLE MANAGED RDB PERSISTENCE" + notify: Redémarrer Redis + + - name: Configurer le nom du fichier AOF + lineinfile: + path: /etc/redis/redis.conf + regexp: "^appendfilename" + line: 'appendfilename "appendonly.aof"' + state: present + notify: Redémarrer Redis + + - name: Désactiver le mode protégé Redis + lineinfile: + path: /etc/redis/redis.conf + regexp: "^protected-mode" + line: "protected-mode no" + state: present + notify: Redémarrer Redis + + - name: Configurer le répertoire de travail Redis + lineinfile: + path: /etc/redis/redis.conf + regexp: "^dir" + line: "dir /var/lib/redis" + state: present + notify: Redémarrer Redis + + - name: Définir le niveau de log + lineinfile: + path: /etc/redis/redis.conf + regexp: "^loglevel" + line: "loglevel notice" + state: present + notify: Redémarrer Redis + + - name: S'assurer que Redis est démarré + systemd: + name: redis-server + state: started + enabled: yes + + handlers: + - name: Redémarrer Redis + systemd: + name: redis-server + state: restarted diff --git a/prod/playbook-prod.yml b/prod/playbook-prod.yml new file mode 100644 index 0000000..7fc9bb0 --- /dev/null +++ b/prod/playbook-prod.yml @@ -0,0 +1,172 @@ +--- +- name: Installation et configuration du frontend et backend + hosts: replica-prod + become: true + gather_facts: true + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - acl + state: present + tags: docker + + - name: Create directory for Docker GPG key + file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + tags: docker + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + tags: docker + + - name: Get system architecture + command: dpkg --print-architecture + register: system_arch + changed_when: false + + - name: Get Ubuntu codename + command: lsb_release -cs + register: ubuntu_codename + changed_when: false + + - name: Add Docker repository + apt_repository: + repo: "deb [arch={{ system_arch.stdout }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename.stdout }} stable" + state: present + filename: docker + tags: docker + + - name: Update apt cache after adding repo + apt: + update_cache: yes + tags: docker + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + tags: docker + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + tags: docker + + - name: Create docker group + group: + name: "{{ docker_group }}" + state: present + tags: docker + + - name: Create docker user + user: + name: "{{ docker_user }}" + group: "{{ docker_group }}" + groups: docker + append: yes + shell: /bin/bash + create_home: yes + state: present + tags: docker + + - name: Add ansible user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + when: ansible_user is defined + tags: docker + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: docker + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + changed_when: false + tags: docker + + - name: Display versions + debug: + msg: + - "{{ docker_version.stdout }}" + - "{{ compose_version.stdout }}" + tags: docker + + - name: Copy Docker project + ansible.builtin.copy: + src: "{{ compose_project_dir }}" + dest: "{{ docker_dir }}/" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + + - name: Ensure parent directory has correct permissions + ansible.builtin.file: + path: "{{ docker_dir }}" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Set correct permissions for Docker files + ansible.builtin.file: + path: "{{ docker_dir }}" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + recurse: yes + tags: project + + - name: Set permissions for .env file + ansible.builtin.file: + path: "{{ docker_dir }}/.env.example" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + tags: project + ignore_errors: yes + + - name: Set permissions for lbtelegram.env file + ansible.builtin.file: + path: "{{ docker_dir }}/.env.lbtelegram" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + tags: project + ignore_errors: yes + + - name: Create certs folder + ansible.builtin.file: + path: "{{ docker_dir }}/certs" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0750" + tags: project diff --git a/prod/playbook-s3-img.yml b/prod/playbook-s3-img.yml new file mode 100644 index 0000000..7cca152 --- /dev/null +++ b/prod/playbook-s3-img.yml @@ -0,0 +1,155 @@ +--- +- name: Installation et configuration du frontend et backend + hosts: bdd-redis-prod + become: true + gather_facts: true + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - acl + state: present + tags: docker + + - name: Create directory for Docker GPG key + file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + tags: docker + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + keyring: /etc/apt/keyrings/docker.gpg + state: present + tags: docker + + - name: Get system architecture + command: dpkg --print-architecture + register: system_arch + changed_when: false + + - name: Get Ubuntu codename + command: lsb_release -cs + register: ubuntu_codename + changed_when: false + + - name: Add Docker repository + apt_repository: + repo: "deb [arch={{ system_arch.stdout }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename.stdout }} stable" + state: present + filename: docker + tags: docker + + - name: Update apt cache after adding repo + apt: + update_cache: yes + tags: docker + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + tags: docker + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + tags: docker + + - name: Create docker group + group: + name: "{{ docker_group }}" + state: present + tags: docker + + - name: Create docker user + user: + name: "{{ docker_user }}" + group: "{{ docker_group }}" + groups: docker + append: yes + shell: /bin/bash + create_home: yes + state: present + tags: docker + + - name: Add ansible user to docker group + user: + name: "{{ user_deploy }}" + groups: docker + append: yes + when: user_deploy is defined + tags: docker + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: docker + + - name: Verify Docker Compose installation + command: docker compose version + register: compose_version + changed_when: false + tags: docker + + - name: Display versions + debug: + msg: + - "{{ docker_version.stdout }}" + - "{{ compose_version.stdout }}" + tags: docker + + - name: Copy Docker project + ansible.builtin.copy: + src: ../../monitoring/s3-img + dest: "{{ docker_dir }}/" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Ensure parent directory has correct permissions + ansible.builtin.file: + path: "{{ docker_dir }}" + state: directory + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0755" + tags: project + + - name: Set correct permissions for Docker files + ansible.builtin.file: + path: "{{ docker_dir }}" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + recurse: yes + tags: project-fin + + - name: Set permissions for .env file + ansible.builtin.file: + path: "{{ docker_dir }}/.env" + owner: "{{ docker_user }}" + group: "{{ docker_group }}" + mode: "0640" + tags: project + ignore_errors: yes diff --git a/templates/60-cis-coredump-limits.conf.j2 b/templates/60-cis-coredump-limits.conf.j2 new file mode 100644 index 0000000..fb2c088 --- /dev/null +++ b/templates/60-cis-coredump-limits.conf.j2 @@ -0,0 +1,2 @@ +# CIS Ubuntu 24.04 - core dumps restreints (1.5.3) +* hard core 0 diff --git a/templates/60-cis-coredump-sysctl.conf.j2 b/templates/60-cis-coredump-sysctl.conf.j2 new file mode 100644 index 0000000..8639854 --- /dev/null +++ b/templates/60-cis-coredump-sysctl.conf.j2 @@ -0,0 +1,2 @@ +# CIS Ubuntu 24.04 - core dumps restreints (1.5.3) +fs.suid_dumpable = 0 diff --git a/templates/60-cis-network-hardening.conf.j2 b/templates/60-cis-network-hardening.conf.j2 new file mode 100644 index 0000000..806ca2a --- /dev/null +++ b/templates/60-cis-network-hardening.conf.j2 @@ -0,0 +1,29 @@ +# CIS Ubuntu 24.04 - durcissement sysctl reseau (3.3.x) +net.ipv4.conf.all.send_redirects = 0 +net.ipv4.conf.default.send_redirects = 0 +net.ipv4.conf.all.accept_redirects = 0 +net.ipv4.conf.default.accept_redirects = 0 +net.ipv6.conf.all.accept_redirects = 0 +net.ipv6.conf.default.accept_redirects = 0 +net.ipv4.conf.all.secure_redirects = 0 +net.ipv4.conf.default.secure_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.conf.all.log_martians = 1 +net.ipv4.conf.default.log_martians = 1 +net.ipv4.tcp_syncookies = 1 +net.ipv6.conf.all.accept_ra = 0 +net.ipv6.conf.default.accept_ra = 0 +net.ipv4.conf.all.rp_filter = 1 +net.ipv4.conf.default.rp_filter = 1 +{% if ip_forwarding_disabled | default(false) %} +# ip_forward desactive : ce host ne fait tourner ni Docker (NAT de ports publies) ni de relais VPN. +net.ipv4.ip_forward = 0 +net.ipv6.conf.all.forwarding = 0 +{% else %} +# ip_forward laisse actif (defaut systeme = 1) : ce host heberge Docker et/ou fait relais VPN, +# le desactiver casserait le NAT des ports publies ou le routage entre pairs WireGuard. +# CIS 35608 restera donc en echec ici par necessite fonctionnelle, pas par oubli. +{% endif %} diff --git a/templates/72_aide_infra_excludes.j2 b/templates/72_aide_infra_excludes.j2 new file mode 100644 index 0000000..4a34ce8 --- /dev/null +++ b/templates/72_aide_infra_excludes.j2 @@ -0,0 +1,18 @@ +# Exclusions specifiques a cette infra (donnees volumineuses, evolution constante, +# integrite geree par leurs propres mecanismes — pas pertinent pour un check AIDE) +!/var/lib/docker$ +!/var/lib/docker/.* +!/var/lib/wazuh-indexer$ +!/var/lib/wazuh-indexer/.* +!/var/lib/postgresql$ +!/var/lib/postgresql/.* +!/var/lib/redis$ +!/var/lib/redis/.* +!/var/lib/clamav$ +!/var/lib/clamav/.* +!/var/lib/beszel-agent$ +!/var/lib/beszel-agent/.* +!/var/log/audit$ +!/var/log/audit/.* +!/var/lib/containerd$ +!/var/lib/containerd/.* diff --git a/templates/audit-50-sudo.rules.j2 b/templates/audit-50-sudo.rules.j2 new file mode 100644 index 0000000..103d1a5 --- /dev/null +++ b/templates/audit-50-sudo.rules.j2 @@ -0,0 +1,12 @@ +# CIS Ubuntu 24.04 - regles d'audit detaillees : surveillance sudo (6.2.3.1-6.2.3.3) + +# 6.2.3.1 - changements de perimetre d'administration (sudoers) +-w /etc/sudoers -p wa -k scope +-w /etc/sudoers.d -p wa -k scope + +# 6.2.3.2 - toute action executee en tant qu'un autre utilisateur (sudo -u, su, etc.) +-a always,exit -F arch=b64 -C euid!=uid -F auid!=unset -S execve -k user_emulation +-a always,exit -F arch=b32 -C euid!=uid -F auid!=unset -S execve -k user_emulation + +# 6.2.3.3 - modifications du fichier de log sudo +-w /var/log/sudo.log -p wa -k sudo_log_file diff --git a/templates/audit-51-omnex.rules.j2 b/templates/audit-51-omnex.rules.j2 new file mode 100644 index 0000000..544d334 --- /dev/null +++ b/templates/audit-51-omnex.rules.j2 @@ -0,0 +1,5 @@ +# Regle custom (hors CIS) - surveillance dediee de l'utilisateur omnex +# auid (login uid) persiste a travers tout sudo/su : capture donc TOUTES les commandes +# executees par omnex, meme apres elevation de privileges. +-a always,exit -F arch=b64 -F auid={{ omnex_uid }} -S execve -k omnex_actions +-a always,exit -F arch=b32 -F auid={{ omnex_uid }} -S execve -k omnex_actions diff --git a/templates/audit-52-cis-rest.rules.j2 b/templates/audit-52-cis-rest.rules.j2 new file mode 100644 index 0000000..502cb67 --- /dev/null +++ b/templates/audit-52-cis-rest.rules.j2 @@ -0,0 +1,76 @@ +# CIS Ubuntu 24.04 - regles d'audit detaillees restantes (6.2.3.4-6.2.3.19) +# UID_MIN lu dynamiquement depuis /etc/login.defs sur cet host : {{ uid_min }} + +# 6.2.3.4 - changements date/heure +-a always,exit -F arch=b64 -S adjtimex,settimeofday -k time-change +-a always,exit -F arch=b32 -S adjtimex,settimeofday -k time-change +-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -k time-change +-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -k time-change +-w /etc/localtime -p wa -k time-change + +# 6.2.3.5 - changements environnement reseau du systeme +-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale +-a always,exit -F arch=b32 -S sethostname,setdomainname -k system-locale +-w /etc/issue -p wa -k system-locale +-w /etc/issue.net -p wa -k system-locale +-w /etc/hosts -p wa -k system-locale +-w /etc/networks -p wa -k system-locale +-w /etc/network/ -p wa -k system-locale +-w /etc/netplan/ -p wa -k system-locale + +# 6.2.3.7 - tentatives d'acces fichier echouees +-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>={{ uid_min }} -F auid!=unset -k access +-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>={{ uid_min }} -F auid!=unset -k access +-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>={{ uid_min }} -F auid!=unset -k access +-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>={{ uid_min }} -F auid!=unset -k access + +# 6.2.3.8 - modifications informations user/group +-w /etc/group -p wa -k identity +-w /etc/passwd -p wa -k identity +-w /etc/gshadow -p wa -k identity +-w /etc/shadow -p wa -k identity +-w /etc/security/opasswd -p wa -k identity +-w /etc/nsswitch.conf -p wa -k identity +-w /etc/pam.conf -p wa -k identity +-w /etc/pam.d -p wa -k identity + +# 6.2.3.9 - modifications permissions discretionnaires (DAC) +-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>={{ uid_min }} -F auid!=unset -F key=perm_mod +-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>={{ uid_min }} -F auid!=unset -F key=perm_mod +-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>={{ uid_min }} -F auid!=unset -F key=perm_mod +-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F auid>={{ uid_min }} -F auid!=unset -F key=perm_mod +-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>={{ uid_min }} -F auid!=unset -F key=perm_mod +-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>={{ uid_min }} -F auid!=unset -F key=perm_mod + +# 6.2.3.10 - montages systeme de fichiers reussis +-a always,exit -F arch=b32 -S mount -F auid>={{ uid_min }} -F auid!=unset -k mounts +-a always,exit -F arch=b64 -S mount -F auid>={{ uid_min }} -F auid!=unset -k mounts + +# 6.2.3.11 - informations d'initiation de session +-w /var/run/utmp -p wa -k session +-w /var/log/wtmp -p wa -k session +-w /var/log/btmp -p wa -k session + +# 6.2.3.12 - evenements login/logout +-w /var/log/lastlog -p wa -k logins +-w /var/run/faillock -p wa -k logins + +# 6.2.3.13 - suppressions de fichiers par les utilisateurs +-a always,exit -F arch=b64 -S rename,unlink,unlinkat,renameat -F auid>={{ uid_min }} -F auid!=unset -F key=delete +-a always,exit -F arch=b32 -S rename,unlink,unlinkat,renameat -F auid>={{ uid_min }} -F auid!=unset -F key=delete + +# 6.2.3.14 - modifications du controle d'acces obligatoire (AppArmor) +-w /etc/apparmor/ -p wa -k MAC-policy +-w /etc/apparmor.d/ -p wa -k MAC-policy + +# 6.2.3.15-17 - commandes chcon / setfacl / chacl +-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>={{ uid_min }} -F auid!=unset -k perm_chng +-a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>={{ uid_min }} -F auid!=unset -k perm_chng +-a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>={{ uid_min }} -F auid!=unset -k perm_chng + +# 6.2.3.18 - commande usermod +-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>={{ uid_min }} -F auid!=unset -k usermod + +# 6.2.3.19 - chargement/dechargement/modification de modules noyau +-a always,exit -F arch=b64 -S init_module,finit_module,delete_module,create_module,query_module -F auid>={{ uid_min }} -F auid!=unset -k kernel_modules +-a always,exit -F path=/usr/bin/kmod -F perm=x -F auid>={{ uid_min }} -F auid!=unset -k kernel_modules diff --git a/templates/blacklist-unused-modules.conf.j2 b/templates/blacklist-unused-modules.conf.j2 new file mode 100644 index 0000000..5fb7b93 --- /dev/null +++ b/templates/blacklist-unused-modules.conf.j2 @@ -0,0 +1,33 @@ +# CIS Ubuntu 24.04 - modules noyau reseau et filesystem inutilises dans cette infra (3.2.1-3.2.4, 1.1.1.10) +install dccp /bin/false +blacklist dccp +install tipc /bin/false +blacklist tipc +install rds /bin/false +blacklist rds +install sctp /bin/false +blacklist sctp +install afs /bin/false +blacklist afs +install ceph /bin/false +blacklist ceph +install cifs /bin/false +blacklist cifs +install exfat /bin/false +blacklist exfat +install ext /bin/false +blacklist ext +# fat et fuse sont compiles en dur dans le noyau Ubuntu (builtin, pas de .ko) : cette entree +# ne change rien fonctionnellement et le check CIS 35509 restera en echec pour ces deux modules. +install fat /bin/false +blacklist fat +install fscache /bin/false +blacklist fscache +install fuse /bin/false +blacklist fuse +install gfs2 /bin/false +blacklist gfs2 +install nfs_common /bin/false +blacklist nfs_common +install nfsd /bin/false +blacklist nfsd diff --git a/templates/cloud_harden.conf.j2 b/templates/cloud_harden.conf.j2 new file mode 100644 index 0000000..b32dc2f --- /dev/null +++ b/templates/cloud_harden.conf.j2 @@ -0,0 +1 @@ +PasswordAuthentication no diff --git a/templates/faillock.conf.j2 b/templates/faillock.conf.j2 new file mode 100644 index 0000000..c4d4b36 --- /dev/null +++ b/templates/faillock.conf.j2 @@ -0,0 +1,5 @@ +# Géré par Ansible (playbook-pam-hardening.yml) — CIS Ubuntu 24.04 (5.5.2/5.5.3) +deny = {{ pam_faillock_deny }} +unlock_time = {{ pam_faillock_unlock_time }} +fail_interval = {{ pam_faillock_fail_interval }} +even_deny_root diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 new file mode 100644 index 0000000..7abd0b4 --- /dev/null +++ b/templates/haproxy.cfg.j2 @@ -0,0 +1,48 @@ +global + log stdout format raw local0 + maxconn 4096 + +defaults + log global + mode http + option httplog + option dontlognull + timeout connect 5s + timeout client 30s + timeout server 30s + timeout http-request 10s + retries 2 + +frontend fe_https + bind *:443 ssl crt /etc/haproxy/certs/mln-uber.club.pem + mode http + option forwardfor + http-request set-header X-Forwarded-Proto https + + # /api/* et /uploads/* -> pool backend (Go), tout le reste -> pool frontend (SPA) + acl is_api path_beg /api/ /uploads/ /webhook/telegram /webhook/nowpayment + use_backend be_api if is_api + default_backend be_frontend + +# ============================================================ +# Pool API — TODO : /api/health n'existe pas encore côté backend, +# ce check échouera tant que la route n'est pas ajoutée. +# ============================================================ +backend be_api + mode http + option httpchk GET /api/health + http-check expect status 200 + + server prod-uber {{ hostvars['prod-uber']['vpn_ip'] }}:443 ssl verify none check inter 2s fall 3 rise 2 + server replica-prod-uber {{ hostvars['replica-prod-uber']['vpn_ip'] | default('TODO-vpn_ip-manquant') }}:443 ssl verify none check inter 2s fall 3 rise 2 backup + +# ============================================================ +# Pool frontend statique (SPA) — check sur "/" (toujours servi par nginx même si l'API est down) +# ============================================================ +backend be_frontend + mode http + option httpchk GET / + http-check expect status 200 + + server prod-uber {{ hostvars['prod-uber']['vpn_ip'] }}:443 ssl verify none check inter 2s fall 3 rise 2 + server replica-prod-uber {{ hostvars['replica-prod-uber']['vpn_ip'] | default('TODO-vpn_ip-manquant') }}:443 ssl verify none check inter 2s fall 3 rise 2 backup diff --git a/templates/pam-configs-faillock-notify.j2 b/templates/pam-configs-faillock-notify.j2 new file mode 100644 index 0000000..136da90 --- /dev/null +++ b/templates/pam-configs-faillock-notify.j2 @@ -0,0 +1,9 @@ +Name: Notify of failed login attempts and reset count upon success +Default: yes +Priority: 1024 +Auth-Type: Primary +Auth: + requisite pam_faillock.so preauth +Account-Type: Primary +Account: + required pam_faillock.so diff --git a/templates/pam-configs-faillock.j2 b/templates/pam-configs-faillock.j2 new file mode 100644 index 0000000..4cc2b5c --- /dev/null +++ b/templates/pam-configs-faillock.j2 @@ -0,0 +1,6 @@ +Name: Enable pam_faillock to deny access +Default: yes +Priority: 0 +Auth-Type: Primary +Auth: + [default=die] pam_faillock.so authfail diff --git a/templates/pam-configs-pwhistory.j2 b/templates/pam-configs-pwhistory.j2 new file mode 100644 index 0000000..3b96c34 --- /dev/null +++ b/templates/pam-configs-pwhistory.j2 @@ -0,0 +1,6 @@ +Name: pwhistory password history checking +Default: yes +Priority: 1024 +Password-Type: Primary +Password: + requisite pam_pwhistory.so remember={{ pam_pwhistory_remember }} enforce_for_root try_first_pass use_authtok diff --git a/templates/pam-configs-pwquality.j2 b/templates/pam-configs-pwquality.j2 new file mode 100644 index 0000000..2dd89c2 --- /dev/null +++ b/templates/pam-configs-pwquality.j2 @@ -0,0 +1,7 @@ +Name: Pwquality password strength checking +Default: yes +Priority: 1024 +Conflicts: cracklib +Password-Type: Primary +Password: + requisite pam_pwquality.so retry=3 diff --git a/templates/pam-configs-unix.j2 b/templates/pam-configs-unix.j2 new file mode 100644 index 0000000..2982ef3 --- /dev/null +++ b/templates/pam-configs-unix.j2 @@ -0,0 +1,23 @@ +Name: Unix authentication +Default: yes +Priority: 256 +Auth-Type: Primary +Auth: + [success=end default=ignore] pam_unix.so try_first_pass +Auth-Initial: + [success=end default=ignore] pam_unix.so +Account-Type: Primary +Account: + [success=end new_authtok_reqd=done default=ignore] pam_unix.so +Account-Initial: + [success=end new_authtok_reqd=done default=ignore] pam_unix.so +Session-Type: Additional +Session: + required pam_unix.so +Session-Initial: + required pam_unix.so +Password-Type: Primary +Password: + [success=end default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt +Password-Initial: + [success=end default=ignore] pam_unix.so obscure yescrypt diff --git a/templates/pwquality.conf.j2 b/templates/pwquality.conf.j2 new file mode 100644 index 0000000..ed3be51 --- /dev/null +++ b/templates/pwquality.conf.j2 @@ -0,0 +1,8 @@ +# Géré par Ansible (playbook-pam-hardening.yml) — CIS Ubuntu 24.04 (5.5.4) +minlen = {{ pam_pwquality_minlen }} +dcredit = -1 +ucredit = -1 +lcredit = -1 +ocredit = -1 +difok = {{ pam_pwquality_difok }} +enforce_for_root diff --git a/templates/ssh_harden_conf.j2 b/templates/ssh_harden_conf.j2 new file mode 100644 index 0000000..46bf664 --- /dev/null +++ b/templates/ssh_harden_conf.j2 @@ -0,0 +1,123 @@ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +Include /etc/ssh/sshd_config.d/*.conf + +#Port 22 +AddressFamily inet +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: +AllowGroups {{ group_ssh }} +AllowUsers {{ user_ssh }} +#LoginGraceTime 2m +PermitRootLogin no +#StrictModes yes +MaxAuthTries 3 +MaxSessions 2 + +PubkeyAuthentication yes + +# Expect .ssh/authorized_keys2 to be disregarded by default in future. +#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +PasswordAuthentication no +#PermitEmptyPasswords no + +# Change to yes to enable challenge-response passwords (beware issues with +# some PAM modules and threads) +KbdInteractiveAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the KbdInteractiveAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via KbdInteractiveAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and KbdInteractiveAuthentication to 'no'. +UsePAM yes + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +X11Forwarding no +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +PrintMotd no +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /run/sshd.pid +MaxStartups 10:30:60 +PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# Allow client to pass locale environment variables +AcceptEnv LANG LC_* + +# override default of no subsystems +Subsystem sftp /usr/lib/openssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server diff --git a/templates/unattended-upgrades-20.j2 b/templates/unattended-upgrades-20.j2 new file mode 100644 index 0000000..1f7f9fc --- /dev/null +++ b/templates/unattended-upgrades-20.j2 @@ -0,0 +1,5 @@ +// Généré par ansible (infra/playbook-unattended-upgrades.yml) — ne pas éditer à la main. +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; +APT::Periodic::Unattended-Upgrade "1"; diff --git a/templates/unattended-upgrades-50.j2 b/templates/unattended-upgrades-50.j2 new file mode 100644 index 0000000..7856598 --- /dev/null +++ b/templates/unattended-upgrades-50.j2 @@ -0,0 +1,30 @@ +// Généré par ansible (infra/playbook-unattended-upgrades.yml) — ne pas éditer à la main. + +Unattended-Upgrade::Origins-Pattern { + "origin=Debian,codename=${distro_codename},label=Debian-Security"; + "origin=Ubuntu,archive=${distro_codename}-security"; +}; + +// Paquets sensibles exclus — mise à jour manuelle et review requises : +// - postgresql/redis : pas de tolérance à une régression sur la DB/cache +// - docker/containerd : un redémarrage du daemon peut interrompre les conteneurs en cours +// - wireguard : composant réseau critique du VPN inter-serveurs +Unattended-Upgrade::Package-Blacklist { + "postgresql*"; + "redis*"; + "docker-ce*"; + "docker-ce-cli*"; + "containerd.io"; + "wireguard*"; +}; + +Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; +Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; + +Unattended-Upgrade::Automatic-Reboot "true"; +Unattended-Upgrade::Automatic-Reboot-WithUsers "true"; +Unattended-Upgrade::Automatic-Reboot-Time "00:00"; + +// Pas d'email (host headless) — notification Telegram via le script/timer dédié. +Unattended-Upgrade::Mail ""; diff --git a/templates/unattended-upgrades-notify.service.j2 b/templates/unattended-upgrades-notify.service.j2 new file mode 100644 index 0000000..016033a --- /dev/null +++ b/templates/unattended-upgrades-notify.service.j2 @@ -0,0 +1,7 @@ +[Unit] +Description=Notification Telegram post unattended-upgrades +After=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/unattended-upgrades-notify.sh diff --git a/templates/unattended-upgrades-notify.sh.j2 b/templates/unattended-upgrades-notify.sh.j2 new file mode 100644 index 0000000..f457b08 --- /dev/null +++ b/templates/unattended-upgrades-notify.sh.j2 @@ -0,0 +1,38 @@ +#!/bin/bash +# Généré par ansible (infra/playbook-unattended-upgrades.yml) — ne pas éditer à la main. +set -euo pipefail + +LOG="/var/log/unattended-upgrades/unattended-upgrades.log" +TODAY="$(date +%Y-%m-%d)" +HOSTNAME="$(hostname)" +WEBHOOK_URL="{{ discord_infra_webhook_url }}" + +if [ -z "$WEBHOOK_URL" ]; then + exit 0 +fi + +INSTALLED="" +if [ -f "$LOG" ]; then + INSTALLED=$(grep "^$TODAY" "$LOG" | grep -i "Packages that will be upgraded\|Packages that are upgraded" || true) +fi + +REBOOT_STATUS="Pas de redémarrage requis" +if [ -f /var/run/reboot-required ]; then + REBOOT_STATUS="⚠️ Redémarrage requis (programmé à 00:00 UTC)" +fi + +if [ -z "$INSTALLED" ]; then + MESSAGE="🔒 [$HOSTNAME] Unattended-upgrades — aucun paquet de sécurité à installer aujourd'hui." +else + MESSAGE="🔒 [$HOSTNAME] Unattended-upgrades — paquets mis à jour aujourd'hui : +${INSTALLED} + +${REBOOT_STATUS}" +fi + +PAYLOAD="$(jq -n --arg content "$MESSAGE" '{content: $content}')" + +curl -s -X POST "$WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + > /dev/null diff --git a/templates/unattended-upgrades-notify.timer.j2 b/templates/unattended-upgrades-notify.timer.j2 new file mode 100644 index 0000000..bb1f417 --- /dev/null +++ b/templates/unattended-upgrades-notify.timer.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=Déclenche la notification Telegram post unattended-upgrades + +[Timer] +OnCalendar=*-*-* 00:30:00 +Persistent=true +RandomizedDelaySec=120 + +[Install] +WantedBy=timers.target