start
This commit is contained in:
1 parent
4b8bb1427a
commit
975d3e9d1d
84 files changed
+2507
-351
No files matched your search
@@ -0,0 +1,40 @@
|
||||
---
|
||||
# Rôle common — prérequis partagés par tous les autres rôles.
|
||||
|
||||
# Distributions acceptées (version majeure minimale).
|
||||
common_supported:
|
||||
Debian: 12
|
||||
Ubuntu: 22
|
||||
RedHat: 8
|
||||
Rocky: 8
|
||||
AlmaLinux: 8
|
||||
OracleLinux: 8
|
||||
|
||||
common_timezone: "Europe/Paris"
|
||||
|
||||
# Paquets de base, communs aux deux familles.
|
||||
common_packages:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- wget
|
||||
- vim
|
||||
- htop
|
||||
- less
|
||||
- rsync
|
||||
- tar
|
||||
- unzip
|
||||
- sudo
|
||||
|
||||
# Paquets propres à chaque famille.
|
||||
common_packages_debian:
|
||||
- apt-transport-https
|
||||
- gnupg2
|
||||
- lsb-release
|
||||
- python3-apt
|
||||
common_packages_redhat:
|
||||
- epel-release
|
||||
- python3-libselinux
|
||||
- dnf-plugins-core
|
||||
|
||||
# Certificats racine internes (files/ca/*.crt) à installer dans le magasin système.
|
||||
common_ca_dir: "{{ playbook_dir }}/files/ca"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Mettre à jour le magasin de certificats
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ common_ca_update_cmd[ansible_facts['os_family']] }}"
|
||||
changed_when: true
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
- name: Vérifier que la distribution est prise en charge
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] in common_supported
|
||||
- ansible_facts['distribution_major_version'] | int >= common_supported[ansible_facts['distribution']]
|
||||
- ansible_facts['os_family'] in ['Debian', 'RedHat']
|
||||
fail_msg: >-
|
||||
{{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
|
||||
n'est pas pris en charge (voir common_supported).
|
||||
|
||||
- name: Exposer le groupe sudo de la famille d'OS
|
||||
ansible.builtin.set_fact:
|
||||
common_sudo_group_effective: "{{ common_sudo_group[ansible_facts['os_family']] }}"
|
||||
|
||||
- name: Rafraîchir le cache APT
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Installer les paquets de base
|
||||
ansible.builtin.package:
|
||||
name: "{{ common_packages + (common_packages_debian if ansible_facts['os_family'] == 'Debian' else common_packages_redhat) }}"
|
||||
state: present
|
||||
|
||||
- name: Régler le fuseau horaire
|
||||
community.general.timezone:
|
||||
name: "{{ common_timezone }}"
|
||||
|
||||
- name: Lister les certificats racine internes à installer
|
||||
ansible.builtin.find:
|
||||
paths: "{{ common_ca_dir }}"
|
||||
patterns: "*.crt"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: common_ca_files
|
||||
|
||||
- name: Installer les certificats racine internes
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.path }}"
|
||||
dest: "{{ common_ca_path[ansible_facts['os_family']] }}/{{ item.path | basename }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ common_ca_files.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
notify: Mettre à jour le magasin de certificats
|
||||
|
||||
- name: Appliquer immédiatement le magasin de certificats
|
||||
ansible.builtin.meta: flush_handlers
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Correspondances propres à chaque famille d'OS.
|
||||
common_ca_path:
|
||||
Debian: /usr/local/share/ca-certificates
|
||||
RedHat: /etc/pki/ca-trust/source/anchors
|
||||
common_ca_update_cmd:
|
||||
Debian: update-ca-certificates
|
||||
RedHat: update-ca-trust extract
|
||||
common_sudo_group:
|
||||
Debian: sudo
|
||||
RedHat: wheel
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# Rôle dns — reprend 07-DNS/resolv.conf, en respectant le gestionnaire de
|
||||
# résolution réellement en place (systemd-resolved, NetworkManager, ou aucun).
|
||||
dns_nameservers: []
|
||||
dns_search_domains: []
|
||||
dns_options:
|
||||
- timeout:2
|
||||
- attempts:2
|
||||
|
||||
# Quand aucun gestionnaire n'est actif, /etc/resolv.conf est écrit directement.
|
||||
# Un client DHCP peut le réécrire : dns_lock_resolv_conf pose l'attribut
|
||||
# immuable (chattr +i) pour l'en empêcher.
|
||||
dns_lock_resolv_conf: false
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Redémarrer systemd-resolved
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-resolved
|
||||
state: restarted
|
||||
|
||||
- name: Recharger NetworkManager
|
||||
ansible.builtin.systemd:
|
||||
name: NetworkManager
|
||||
state: reloaded
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Vérifier qu'au moins un serveur DNS est défini
|
||||
ansible.builtin.assert:
|
||||
that: dns_nameservers | length > 0
|
||||
fail_msg: "dns_nameservers est vide"
|
||||
|
||||
- name: Recenser les services de résolution présents
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Déterminer le gestionnaire de résolution en place
|
||||
ansible.builtin.set_fact:
|
||||
dns_manager: >-
|
||||
{{ 'resolved' if (ansible_facts['services']['systemd-resolved.service'] | default({})).state | default('') == 'running'
|
||||
else 'networkmanager' if (ansible_facts['services']['NetworkManager.service'] | default({})).state | default('') == 'running'
|
||||
else 'static' }}
|
||||
|
||||
- name: Configurer la résolution
|
||||
ansible.builtin.include_tasks: "{{ dns_manager }}.yml"
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Empêcher NetworkManager de gérer /etc/resolv.conf
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/NetworkManager/conf.d/90-dns-none.conf
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
[main]
|
||||
dns=none
|
||||
rc-manager=unmanaged
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Recharger NetworkManager
|
||||
|
||||
- name: Écrire /etc/resolv.conf
|
||||
ansible.builtin.include_tasks: static.yml
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
- name: Créer le répertoire de surcharge de systemd-resolved
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Déposer la configuration DNS de systemd-resolved
|
||||
ansible.builtin.template:
|
||||
src: resolved.conf.j2
|
||||
dest: /etc/systemd/resolved.conf.d/10-dns.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Redémarrer systemd-resolved
|
||||
|
||||
- name: Pointer /etc/resolv.conf vers le résolveur local
|
||||
ansible.builtin.file:
|
||||
src: /run/systemd/resolve/stub-resolv.conf
|
||||
dest: /etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
- name: Examiner /etc/resolv.conf
|
||||
ansible.builtin.stat:
|
||||
path: /etc/resolv.conf
|
||||
follow: false
|
||||
register: dns_resolv_stat
|
||||
|
||||
- name: Supprimer un éventuel lien symbolique /etc/resolv.conf
|
||||
ansible.builtin.file:
|
||||
path: /etc/resolv.conf
|
||||
state: absent
|
||||
when: dns_resolv_stat.stat.islnk | default(false)
|
||||
|
||||
- name: Retirer l'attribut immuable avant écriture
|
||||
ansible.builtin.file:
|
||||
path: /etc/resolv.conf
|
||||
attributes: -i
|
||||
when:
|
||||
- dns_resolv_stat.stat.exists
|
||||
- not dns_resolv_stat.stat.islnk | default(false)
|
||||
- "'i' in (dns_resolv_stat.stat.attributes | default([]))"
|
||||
|
||||
- name: Écrire /etc/resolv.conf
|
||||
ansible.builtin.template:
|
||||
src: resolv.conf.j2
|
||||
dest: /etc/resolv.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Poser l'attribut immuable sur /etc/resolv.conf
|
||||
ansible.builtin.file:
|
||||
path: /etc/resolv.conf
|
||||
attributes: +i
|
||||
when: dns_lock_resolv_conf | bool
|
||||
@@ -0,0 +1,10 @@
|
||||
# {{ ansible_managed }}
|
||||
{% for ns in dns_nameservers %}
|
||||
nameserver {{ ns }}
|
||||
{% endfor %}
|
||||
{% if dns_search_domains | length > 0 %}
|
||||
search {{ dns_search_domains | join(' ') }}
|
||||
{% endif %}
|
||||
{% if dns_options | length > 0 %}
|
||||
options {{ dns_options | join(' ') }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,6 @@
|
||||
# {{ ansible_managed }}
|
||||
[Resolve]
|
||||
DNS={{ dns_nameservers | join(' ') }}
|
||||
{% if dns_search_domains | length > 0 %}
|
||||
Domains={{ dns_search_domains | join(' ') }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
# Rôle fail2ban — bannissement des sources en échec d'authentification SSH.
|
||||
fail2ban_bantime: 1h
|
||||
fail2ban_findtime: 10m
|
||||
fail2ban_maxretry: 5
|
||||
# Adresses jamais bannies : boucle locale + réseaux d'administration.
|
||||
fail2ban_ignoreip: "{{ ['127.0.0.1/8', '::1'] + (firewall_ssh_sources | default([])) }}"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Redémarrer fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Installer fail2ban
|
||||
ansible.builtin.package:
|
||||
name: "{{ fail2ban_packages[ansible_facts['os_family']] }}"
|
||||
state: present
|
||||
|
||||
- name: Déposer la configuration locale
|
||||
ansible.builtin.template:
|
||||
src: jail.local.j2
|
||||
dest: /etc/fail2ban/jail.local
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Redémarrer fail2ban
|
||||
|
||||
- name: Activer et démarrer fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,16 @@
|
||||
# {{ ansible_managed }}
|
||||
[DEFAULT]
|
||||
# Le journal systemd est lu directement : aucun chemin de log à maintenir
|
||||
# selon la distribution.
|
||||
backend = systemd
|
||||
banaction = {{ fail2ban_banaction[ansible_facts['os_family']] }}
|
||||
banaction_allports = {{ fail2ban_banaction[ansible_facts['os_family']] }}
|
||||
bantime = {{ fail2ban_bantime }}
|
||||
findtime = {{ fail2ban_findtime }}
|
||||
maxretry = {{ fail2ban_maxretry }}
|
||||
ignoreip = {{ fail2ban_ignoreip | join(' ') }}
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = {{ ssh_port | default(22) }}
|
||||
mode = aggressive
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
fail2ban_packages:
|
||||
Debian: [fail2ban, python3-systemd]
|
||||
RedHat: [fail2ban, fail2ban-firewalld]
|
||||
# Action de bannissement cohérente avec le pare-feu de la famille.
|
||||
fail2ban_banaction:
|
||||
Debian: nftables-multiport
|
||||
RedHat: firewallcmd-rich-rules
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# Rôle firewall — nouveau par rapport aux scripts d'origine (qui ne posaient
|
||||
# aucun pare-feu). Politique : tout est fermé en entrée sauf SSH, ICMP et les
|
||||
# ports de supervision, chacun limité aux réseaux qui en ont besoin.
|
||||
#
|
||||
# auto : nftables sous Debian/Ubuntu, firewalld sous Red Hat (chacun est le
|
||||
# pare-feu natif de sa famille). Peut être forcé à « nftables » ou « firewalld ».
|
||||
firewall_backend: auto
|
||||
|
||||
# Réseaux autorisés à joindre SSH. Vide = tout le monde.
|
||||
firewall_ssh_sources: []
|
||||
|
||||
# Règles standard, activées avec le rôle correspondant.
|
||||
firewall_rules:
|
||||
- name: snmp
|
||||
port: 161
|
||||
proto: udp
|
||||
sources: "{{ supervision_sources | default([]) }}"
|
||||
enabled: "{{ enable_snmp | default(false) }}"
|
||||
- name: nrpe
|
||||
port: 5666
|
||||
proto: tcp
|
||||
sources: "{{ supervision_sources | default([]) }}"
|
||||
enabled: "{{ enable_nrpe | default(false) }}"
|
||||
|
||||
# Règles supplémentaires propres à un hôte ou un groupe (même format).
|
||||
# Exemple : { name: https, port: 443, proto: tcp, sources: [], enabled: true }
|
||||
firewall_extra_rules: []
|
||||
|
||||
# Avant d'appliquer, vérifier que l'adresse depuis laquelle Ansible est
|
||||
# connecté figure bien dans firewall_ssh_sources (sinon : verrouillage).
|
||||
firewall_check_controller_source: true
|
||||
# Après application, ouvrir une nouvelle session SSH depuis le contrôleur.
|
||||
firewall_verify_ssh: true
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Recharger nftables
|
||||
ansible.builtin.systemd:
|
||||
name: nftables
|
||||
state: reloaded
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
- name: Installer firewalld
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- firewalld
|
||||
- python3-firewall
|
||||
state: present
|
||||
|
||||
- name: Recenser les services
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Arrêter et désactiver nftables (pilotage par firewalld)
|
||||
ansible.builtin.systemd:
|
||||
name: nftables
|
||||
enabled: false
|
||||
state: stopped
|
||||
when: "'nftables.service' in ansible_facts['services']"
|
||||
|
||||
- name: Activer et démarrer firewalld
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Retirer les services ouverts par défaut dans la zone public
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
service: "{{ item }}"
|
||||
state: disabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop:
|
||||
- cockpit
|
||||
- dhcpv6-client
|
||||
- ssh
|
||||
|
||||
- name: Autoriser SSH depuis tout le monde (aucune source définie)
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
port: "{{ ssh_port | default(22) }}/tcp"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
when: firewall_ssh_sources | length == 0
|
||||
|
||||
- name: Autoriser SSH depuis les réseaux d'administration
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
rich_rule: >-
|
||||
rule family="{{ 'ipv6' if ':' in item else 'ipv4' }}" source address="{{ item }}"
|
||||
port port="{{ ssh_port | default(22) }}" protocol="tcp" accept
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop: "{{ firewall_ssh_sources }}"
|
||||
|
||||
- name: Autoriser les services depuis leurs sources
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
rich_rule: >-
|
||||
rule family="{{ 'ipv6' if ':' in item.1 else 'ipv4' }}" source address="{{ item.1 }}"
|
||||
port port="{{ item.0.port }}" protocol="{{ item.0.proto }}" accept
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop: "{{ firewall_rules_effective | subelements('sources', skip_missing=True) }}"
|
||||
loop_control:
|
||||
label: "{{ item.0.name }} <- {{ item.1 }}"
|
||||
|
||||
- name: Autoriser les services ouverts à tous (sans source)
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
port: "{{ item.port }}/{{ item.proto }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop: "{{ firewall_rules_effective | rejectattr('sources') | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
- name: Déterminer le pare-feu à utiliser
|
||||
ansible.builtin.set_fact:
|
||||
firewall_backend_effective: >-
|
||||
{{ firewall_backend if firewall_backend != 'auto' else firewall_backend_by_family[ansible_facts['os_family']] }}
|
||||
firewall_rules_effective: >-
|
||||
{{ (firewall_rules + firewall_extra_rules) | selectattr('enabled', 'defined') | selectattr('enabled') | list }}
|
||||
|
||||
- name: Relever l'adresse source de la session Ansible
|
||||
ansible.builtin.shell:
|
||||
cmd: 'echo "${SSH_CONNECTION%% *}"'
|
||||
become: false
|
||||
register: firewall_controller_ip
|
||||
changed_when: false
|
||||
when:
|
||||
- firewall_check_controller_source | bool
|
||||
- firewall_ssh_sources | length > 0
|
||||
|
||||
- name: Refuser une liste firewall_ssh_sources qui exclurait le contrôleur
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- python3
|
||||
- -c
|
||||
- |
|
||||
import ipaddress, sys
|
||||
ip = ipaddress.ip_address(sys.argv[1])
|
||||
ok = any(ip in ipaddress.ip_network(n, strict=False) for n in ' '.join(sys.argv[2:]).split())
|
||||
sys.exit(0 if ok else 1)
|
||||
- "{{ firewall_controller_ip.stdout }}"
|
||||
- "{{ firewall_ssh_sources | join(' ') }}"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
register: firewall_controller_check
|
||||
when:
|
||||
- firewall_check_controller_source | bool
|
||||
- firewall_ssh_sources | length > 0
|
||||
- firewall_controller_ip.stdout | length > 0
|
||||
|
||||
- name: Arrêter si le contrôleur serait bloqué
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Ansible est connecté depuis {{ firewall_controller_ip.stdout }}, absent de
|
||||
firewall_ssh_sources ({{ firewall_ssh_sources | join(', ') }}) : appliquer
|
||||
ce pare-feu couperait l'accès. Compléter la liste ou passer
|
||||
firewall_check_controller_source=false en connaissance de cause.
|
||||
when:
|
||||
- firewall_controller_check is not skipped
|
||||
- firewall_controller_check.rc != 0
|
||||
|
||||
- name: Configurer le pare-feu
|
||||
ansible.builtin.include_tasks: "{{ firewall_backend_effective }}.yml"
|
||||
|
||||
- name: Appliquer les règles maintenant
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Vérifier qu'une nouvelle connexion SSH aboutit
|
||||
ansible.builtin.include_role:
|
||||
name: ssh
|
||||
tasks_from: verify
|
||||
when: firewall_verify_ssh | bool
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
- name: Installer nftables
|
||||
ansible.builtin.package:
|
||||
name: nftables
|
||||
state: present
|
||||
|
||||
- name: Recenser les services
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Arrêter et désactiver les pare-feux concurrents
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: false
|
||||
state: stopped
|
||||
loop:
|
||||
- ufw
|
||||
- firewalld
|
||||
- iptables
|
||||
- netfilter-persistent
|
||||
when: (item ~ '.service') in ansible_facts['services']
|
||||
|
||||
- name: Créer le répertoire des fragments de règles
|
||||
ansible.builtin.file:
|
||||
path: /etc/nftables.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
- name: Déposer un fragment vide pour que l'include soit toujours valide
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/nftables.d/00-placeholder.nft
|
||||
content: |
|
||||
# Fragments de règles applicatives (un fichier par service). Ne rien mettre ici.
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
force: false
|
||||
|
||||
- name: Déposer le jeu de règles principal
|
||||
ansible.builtin.template:
|
||||
src: nftables.conf.j2
|
||||
dest: /etc/nftables.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
validate: nft -c -f %s
|
||||
notify: Recharger nftables
|
||||
|
||||
- name: Activer et démarrer nftables
|
||||
ansible.builtin.systemd:
|
||||
name: nftables
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/sbin/nft -f
|
||||
# {{ ansible_managed }}
|
||||
# Compatible avec le jeu posé par cloud-init sur les VM Terraform :
|
||||
# les règles applicatives vont dans /etc/nftables.d/*.nft.
|
||||
flush ruleset
|
||||
|
||||
table inet filter {
|
||||
{% if firewall_ssh_sources | length > 0 %}
|
||||
set reseaux_admin_v4 {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
{% set v4 = firewall_ssh_sources | reject('search', ':') | list %}
|
||||
{% if v4 | length > 0 %}
|
||||
elements = { {{ v4 | join(', ') }} }
|
||||
{% endif %}
|
||||
}
|
||||
set reseaux_admin_v6 {
|
||||
type ipv6_addr
|
||||
flags interval
|
||||
{% set v6 = firewall_ssh_sources | select('search', ':') | list %}
|
||||
{% if v6 | length > 0 %}
|
||||
elements = { {{ v6 | join(', ') }} }
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
|
||||
iif "lo" accept
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
|
||||
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded, parameter-problem } accept
|
||||
ip6 nexthdr ipv6-icmp accept
|
||||
|
||||
{% if firewall_ssh_sources | length > 0 %}
|
||||
tcp dport {{ ssh_port | default(22) }} ip saddr @reseaux_admin_v4 ct state new accept
|
||||
tcp dport {{ ssh_port | default(22) }} ip6 saddr @reseaux_admin_v6 ct state new accept
|
||||
{% else %}
|
||||
tcp dport {{ ssh_port | default(22) }} ct state new accept
|
||||
{% endif %}
|
||||
|
||||
{% for rule in firewall_rules_effective %}
|
||||
{% set r4 = rule.sources | reject('search', ':') | list %}
|
||||
{% set r6 = rule.sources | select('search', ':') | list %}
|
||||
# {{ rule.name }}
|
||||
{% if rule.sources | length == 0 %}
|
||||
{{ rule.proto }} dport {{ rule.port }} accept
|
||||
{% else %}
|
||||
{% if r4 | length > 0 %}
|
||||
ip saddr { {{ r4 | join(', ') }} } {{ rule.proto }} dport {{ rule.port }} accept
|
||||
{% endif %}
|
||||
{% if r6 | length > 0 %}
|
||||
ip6 saddr { {{ r6 | join(', ') }} } {{ rule.proto }} dport {{ rule.port }} accept
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
include "/etc/nftables.d/*.nft"
|
||||
|
||||
counter comment "paquets rejetes en entree"
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop;
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority filter; policy accept;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
firewall_backend_by_family:
|
||||
Debian: nftables
|
||||
RedHat: firewalld
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
# Rôle glpi_agent — reprend 04-agent-glpi (installateur perl officiel).
|
||||
# Le certificat racine n'est plus passé à l'agent : il est installé dans le
|
||||
# magasin système par le rôle common, ce qui couvre l'agent et tout le reste.
|
||||
glpi_agent_version: "1.19"
|
||||
glpi_agent_installer_url: >-
|
||||
https://github.com/glpi-project/glpi-agent/releases/download/{{ glpi_agent_version
|
||||
}}/glpi-agent-{{ glpi_agent_version }}-linux-installer.pl
|
||||
glpi_agent_server: ""
|
||||
glpi_agent_tag: ""
|
||||
# Profil d'installation de l'installateur : typical | network | all.
|
||||
glpi_agent_install_type: all
|
||||
# Tâches activées côté agent : inventory, netdiscovery, netinventory, deploy, collect, esx…
|
||||
glpi_agent_tasks: "inventory,deploy,collect"
|
||||
glpi_agent_no_ssl_check: false
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Redémarrer glpi-agent
|
||||
ansible.builtin.systemd:
|
||||
name: glpi-agent
|
||||
state: restarted
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
- name: Vérifier que le serveur GLPI est renseigné
|
||||
ansible.builtin.assert:
|
||||
that: glpi_agent_server is match('^https?://')
|
||||
fail_msg: "glpi_agent_server doit être l'URL du serveur GLPI (plugin inventaire)"
|
||||
|
||||
- name: Installer perl (prérequis de l'installateur)
|
||||
ansible.builtin.package:
|
||||
name: perl
|
||||
state: present
|
||||
|
||||
- name: Lire la version installée de l'agent
|
||||
ansible.builtin.command:
|
||||
cmd: glpi-agent --version
|
||||
register: glpi_agent_installed
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Installer ou mettre à jour l'agent
|
||||
when: ('(' ~ glpi_agent_version ~ ')') not in (glpi_agent_installed.stdout | default(''))
|
||||
environment: "{{ {'http_proxy': proxy_url, 'https_proxy': proxy_url} if (proxy_url | default('')) | length > 0 else {} }}"
|
||||
block:
|
||||
- name: Télécharger l'installateur
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ glpi_agent_installer_url }}"
|
||||
dest: "/tmp/glpi-agent-{{ glpi_agent_version }}-linux-installer.pl"
|
||||
mode: "0600"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Exécuter l'installateur
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- perl
|
||||
- "/tmp/glpi-agent-{{ glpi_agent_version }}-linux-installer.pl"
|
||||
- --install
|
||||
- --type={{ glpi_agent_install_type }}
|
||||
- --no-question
|
||||
changed_when: true
|
||||
|
||||
- name: Supprimer l'installateur
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/glpi-agent-{{ glpi_agent_version }}-linux-installer.pl"
|
||||
state: absent
|
||||
|
||||
- name: Configurer l'agent
|
||||
ansible.builtin.template:
|
||||
src: 10-ansible.cfg.j2
|
||||
dest: /etc/glpi-agent/conf.d/10-ansible.cfg
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
notify: Redémarrer glpi-agent
|
||||
|
||||
- name: Activer et démarrer l'agent
|
||||
ansible.builtin.systemd:
|
||||
name: glpi-agent
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,10 @@
|
||||
# {{ ansible_managed }}
|
||||
server = {{ glpi_agent_server }}
|
||||
{% if glpi_agent_tag | length > 0 %}
|
||||
tag = {{ glpi_agent_tag }}
|
||||
{% endif %}
|
||||
tasks = {{ glpi_agent_tasks }}
|
||||
no-ssl-check = {{ '1' if glpi_agent_no_ssl_check else '0' }}
|
||||
{% if proxy_url | default('') | length > 0 %}
|
||||
proxy = {{ proxy_url }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# Rôle nrpe — reprend 08-NRPE (installation) et ajoute la configuration :
|
||||
# hôtes autorisés et commandes de base.
|
||||
nrpe_allowed_hosts: []
|
||||
nrpe_port: 5666
|
||||
# Vérifications standard exposées au serveur de supervision.
|
||||
nrpe_commands:
|
||||
check_users: "check_users -w 5 -c 10"
|
||||
check_load: "check_load -r -w .15,.10,.05 -c .30,.25,.20"
|
||||
check_disk_root: "check_disk -w 20% -c 10% -p /"
|
||||
check_zombie_procs: "check_procs -w 5 -c 10 -s Z"
|
||||
check_total_procs: "check_procs -w 250 -c 400"
|
||||
check_swap: "check_swap -w 20% -c 10%"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Redémarrer nrpe
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ nrpe_service[ansible_facts['os_family']] }}"
|
||||
state: restarted
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
- name: Installer NRPE et les greffons
|
||||
ansible.builtin.package:
|
||||
name: "{{ nrpe_packages[ansible_facts['os_family']] }}"
|
||||
state: present
|
||||
|
||||
- name: Vérifier qu'au moins un hôte de supervision est défini
|
||||
ansible.builtin.assert:
|
||||
that: nrpe_allowed_hosts | length > 0
|
||||
fail_msg: "nrpe_allowed_hosts est vide"
|
||||
|
||||
- name: Fixer les hôtes autorisés dans nrpe.cfg
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/nagios/nrpe.cfg
|
||||
regexp: '^\s*#?\s*allowed_hosts\s*='
|
||||
line: "allowed_hosts={{ (['127.0.0.1', '::1'] + nrpe_allowed_hosts) | join(',') }}"
|
||||
notify: Redémarrer nrpe
|
||||
|
||||
- name: Interdire les arguments passés par le superviseur
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/nagios/nrpe.cfg
|
||||
regexp: '^\s*#?\s*dont_blame_nrpe\s*='
|
||||
line: "dont_blame_nrpe=0"
|
||||
notify: Redémarrer nrpe
|
||||
|
||||
- name: Fixer le port d'écoute
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/nagios/nrpe.cfg
|
||||
regexp: '^\s*#?\s*server_port\s*='
|
||||
line: "server_port={{ nrpe_port }}"
|
||||
notify: Redémarrer nrpe
|
||||
|
||||
- name: Créer le répertoire des commandes
|
||||
ansible.builtin.file:
|
||||
path: "{{ nrpe_include_dir[ansible_facts['os_family']] }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Déposer les commandes de supervision
|
||||
ansible.builtin.template:
|
||||
src: commandes.cfg.j2
|
||||
dest: "{{ nrpe_include_dir[ansible_facts['os_family']] }}/10-ansible.cfg"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Redémarrer nrpe
|
||||
|
||||
- name: Activer et démarrer NRPE
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ nrpe_service[ansible_facts['os_family']] }}"
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,4 @@
|
||||
# {{ ansible_managed }}
|
||||
{% for name, cmd in nrpe_commands.items() %}
|
||||
command[{{ name }}]={{ nrpe_plugins_dir[ansible_facts['os_family']] }}/{{ cmd }}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
nrpe_packages:
|
||||
Debian: [nagios-nrpe-server, monitoring-plugins-basic, monitoring-plugins-standard]
|
||||
RedHat: [nrpe, nagios-plugins-all]
|
||||
nrpe_service:
|
||||
Debian: nagios-nrpe-server
|
||||
RedHat: nrpe
|
||||
nrpe_include_dir:
|
||||
Debian: /etc/nagios/nrpe.d
|
||||
RedHat: /etc/nrpe.d
|
||||
nrpe_plugins_dir:
|
||||
Debian: /usr/lib/nagios/plugins
|
||||
RedHat: /usr/lib64/nagios/plugins
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
# Rôle os_hardening — durcissement du système, sans jamais toucher à
|
||||
# l'authentification locale (PAM, mots de passe, console) : un administrateur
|
||||
# devant la machine doit toujours pouvoir se connecter.
|
||||
|
||||
# Paramètres noyau (repris du cloud-init Terraform et complétés).
|
||||
os_hardening_sysctl:
|
||||
net.ipv4.conf.all.accept_redirects: 0
|
||||
net.ipv4.conf.default.accept_redirects: 0
|
||||
net.ipv4.conf.all.secure_redirects: 0
|
||||
net.ipv4.conf.default.secure_redirects: 0
|
||||
net.ipv4.conf.all.send_redirects: 0
|
||||
net.ipv4.conf.default.send_redirects: 0
|
||||
net.ipv4.conf.all.accept_source_route: 0
|
||||
net.ipv4.conf.default.accept_source_route: 0
|
||||
net.ipv4.conf.all.log_martians: 1
|
||||
net.ipv4.conf.default.log_martians: 1
|
||||
net.ipv4.conf.all.rp_filter: 1
|
||||
net.ipv4.conf.default.rp_filter: 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts: 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses: 1
|
||||
net.ipv4.tcp_syncookies: 1
|
||||
net.ipv6.conf.all.accept_redirects: 0
|
||||
net.ipv6.conf.default.accept_redirects: 0
|
||||
net.ipv6.conf.all.accept_source_route: 0
|
||||
net.ipv6.conf.all.accept_ra: 0
|
||||
net.ipv6.conf.default.accept_ra: 0
|
||||
kernel.dmesg_restrict: 1
|
||||
kernel.kptr_restrict: 2
|
||||
kernel.sysrq: 0
|
||||
kernel.yama.ptrace_scope: 1
|
||||
kernel.randomize_va_space: 2
|
||||
fs.protected_hardlinks: 1
|
||||
fs.protected_symlinks: 1
|
||||
fs.protected_fifos: 2
|
||||
fs.protected_regular: 2
|
||||
fs.suid_dumpable: 0
|
||||
|
||||
# Interdire les core dumps.
|
||||
os_hardening_disable_core_dumps: true
|
||||
|
||||
# Politique des NOUVEAUX comptes (login.defs) — n'affecte pas les comptes existants.
|
||||
os_hardening_login_defs:
|
||||
PASS_MAX_DAYS: "365"
|
||||
PASS_MIN_DAYS: "1"
|
||||
PASS_WARN_AGE: "14"
|
||||
UMASK: "027"
|
||||
|
||||
# Journalisation des appels système sensibles.
|
||||
os_hardening_auditd: true
|
||||
|
||||
# Services inutiles sur un serveur, désactivés s'ils sont présents.
|
||||
os_hardening_disable_services:
|
||||
- avahi-daemon
|
||||
- cups
|
||||
- bluetooth
|
||||
|
||||
# Paquets à retirer (protocoles en clair).
|
||||
os_hardening_remove_packages:
|
||||
- telnet
|
||||
- rsh-client
|
||||
- rsh-redone-client
|
||||
- talk
|
||||
- nis
|
||||
- ypbind
|
||||
|
||||
# Restreindre « su » aux membres du groupe sudo/wheel (pam_wheel).
|
||||
# false par défaut : les comptes sans sudo doivent pouvoir faire « su - »
|
||||
# (cas des VM Terraform où sudo est purgé).
|
||||
os_hardening_restrict_su: false
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
- name: Appliquer les paramètres noyau
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item.key }}"
|
||||
value: "{{ item.value }}"
|
||||
sysctl_file: /etc/sysctl.d/99-durcissement.conf
|
||||
sysctl_set: true
|
||||
reload: true
|
||||
state: present
|
||||
loop: "{{ os_hardening_sysctl | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
# Certaines clés n'existent pas partout (yama, IPv6 désactivé) : ne pas bloquer.
|
||||
failed_when: false
|
||||
|
||||
- name: Interdire les core dumps (limits)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/security/limits.d/99-durcissement.conf
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
* hard core 0
|
||||
* soft core 0
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: os_hardening_disable_core_dumps | bool
|
||||
|
||||
- name: Créer le répertoire coredump.conf.d
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/coredump.conf.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: os_hardening_disable_core_dumps | bool
|
||||
|
||||
|
||||
- name: Interdire les core dumps (systemd-coredump)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/coredump.conf.d/99-durcissement.conf
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
[Coredump]
|
||||
Storage=none
|
||||
ProcessSizeMax=0
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: os_hardening_disable_core_dumps | bool
|
||||
|
||||
- name: Politique des nouveaux comptes (login.defs)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/login.defs
|
||||
regexp: '^\s*#?\s*{{ item.key }}\s'
|
||||
line: "{{ item.key }}\t{{ item.value }}"
|
||||
loop: "{{ os_hardening_login_defs | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Installer et activer auditd
|
||||
ansible.builtin.package:
|
||||
name: "{{ 'auditd' if ansible_facts['os_family'] == 'Debian' else 'audit' }}"
|
||||
state: present
|
||||
when: os_hardening_auditd | bool
|
||||
|
||||
- name: Démarrer auditd
|
||||
ansible.builtin.systemd:
|
||||
name: auditd
|
||||
enabled: true
|
||||
state: started
|
||||
when: os_hardening_auditd | bool
|
||||
|
||||
- name: Recenser les services
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Désactiver les services inutiles présents
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: false
|
||||
state: stopped
|
||||
masked: true
|
||||
loop: "{{ os_hardening_disable_services }}"
|
||||
when: (item ~ '.service') in ansible_facts['services']
|
||||
|
||||
- name: Retirer les paquets aux protocoles en clair
|
||||
ansible.builtin.package:
|
||||
name: "{{ os_hardening_remove_packages }}"
|
||||
state: absent
|
||||
|
||||
- name: Restreindre « su » au groupe sudo/wheel
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/pam.d/su
|
||||
regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so'
|
||||
line: "auth required pam_wheel.so use_uid group={{ common_sudo_group_effective }}"
|
||||
insertafter: '^auth\s+sufficient\s+pam_rootok\.so'
|
||||
when: os_hardening_restrict_su | bool
|
||||
|
||||
- name: Protéger les fichiers sensibles
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { path: /etc/crontab, mode: "0600" }
|
||||
- { path: /etc/cron.d, mode: "0700" }
|
||||
- { path: /etc/cron.daily, mode: "0700" }
|
||||
- { path: /etc/cron.hourly, mode: "0700" }
|
||||
- { path: /etc/cron.weekly, mode: "0700" }
|
||||
- { path: /etc/cron.monthly, mode: "0700" }
|
||||
- { path: /etc/ssh/sshd_config, mode: "0600" }
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
failed_when: false
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Rôle proxy — reprend 02-apt/02proxy et les exports du README d'origine.
|
||||
proxy_url: ""
|
||||
proxy_no_proxy:
|
||||
- localhost
|
||||
- 127.0.0.1
|
||||
|
||||
# Le gestionnaire de paquets passe toujours par le proxy.
|
||||
# L'environnement (profile.d) est optionnel : utile pour wget/curl/pip.
|
||||
proxy_configure_environment: true
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
- name: Vérifier que l'URL du proxy est renseignée
|
||||
ansible.builtin.assert:
|
||||
that: proxy_url is match('^https?://')
|
||||
fail_msg: "proxy_url doit être une URL http(s)://hôte:port"
|
||||
|
||||
- name: Configurer le proxy APT
|
||||
ansible.builtin.template:
|
||||
src: 02proxy.j2
|
||||
dest: /etc/apt/apt.conf.d/02proxy
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Configurer le proxy DNF
|
||||
community.general.ini_file:
|
||||
path: /etc/dnf/dnf.conf
|
||||
section: main
|
||||
option: proxy
|
||||
value: "{{ proxy_url }}"
|
||||
no_extra_spaces: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ansible_facts['os_family'] == 'RedHat'
|
||||
|
||||
- name: Exposer le proxy dans l'environnement des sessions
|
||||
ansible.builtin.template:
|
||||
src: proxy.sh.j2
|
||||
dest: /etc/profile.d/proxy.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: proxy_configure_environment | bool
|
||||
|
||||
- name: Retirer le proxy de l'environnement des sessions
|
||||
ansible.builtin.file:
|
||||
path: /etc/profile.d/proxy.sh
|
||||
state: absent
|
||||
when: not proxy_configure_environment | bool
|
||||
@@ -0,0 +1,4 @@
|
||||
// {{ ansible_managed }}
|
||||
Acquire::http::proxy "{{ proxy_url }}";
|
||||
Acquire::https::proxy "{{ proxy_url }}";
|
||||
Acquire::ftp::proxy "{{ proxy_url }}";
|
||||
@@ -0,0 +1,6 @@
|
||||
# {{ ansible_managed }}
|
||||
export http_proxy="{{ proxy_url }}"
|
||||
export https_proxy="{{ proxy_url }}"
|
||||
export ftp_proxy="{{ proxy_url }}"
|
||||
export no_proxy="{{ proxy_no_proxy | join(',') }}"
|
||||
export HTTP_PROXY="$http_proxy" HTTPS_PROXY="$https_proxy" FTP_PROXY="$ftp_proxy" NO_PROXY="$no_proxy"
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# Rôle snmp — reprend 06-snmp (communautés v2c par source de supervision)
|
||||
# et ajoute SNMPv3 en option.
|
||||
snmp_listen: "udp:161,udp6:[::1]:161"
|
||||
snmp_sys_location: "Datacenter"
|
||||
snmp_sys_contact: "[email protected]"
|
||||
|
||||
# Communautés v2c en lecture seule : name (depuis le vault), sources (liste ;
|
||||
# vide = toutes). Reprise de l'ancien snmpd.conf.
|
||||
snmp_communities: []
|
||||
|
||||
# Utilisateurs SNMPv3 (lecture seule) : name, auth_pass, priv_pass, et en
|
||||
# option auth (SHA) / priv (AES). Créés une fois dans la base persistante.
|
||||
snmp_v3_users: []
|
||||
|
||||
# Lignes « extend » (ex. docker-stats). Le script doit être déployé à part.
|
||||
snmp_extends: []
|
||||
# - { name: docker, command: /etc/snmp/docker-stats.py }
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Redémarrer snmpd
|
||||
ansible.builtin.systemd:
|
||||
name: snmpd
|
||||
state: restarted
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: Installer snmpd
|
||||
ansible.builtin.package:
|
||||
name: "{{ snmp_packages[ansible_facts['os_family']] }}"
|
||||
state: present
|
||||
|
||||
- name: Vérifier qu'au moins un accès est défini
|
||||
ansible.builtin.assert:
|
||||
that: (snmp_communities | length) + (snmp_v3_users | length) > 0
|
||||
fail_msg: "snmp_communities et snmp_v3_users sont vides : aucun superviseur ne pourrait interroger l'agent"
|
||||
|
||||
- name: Déposer snmpd.conf
|
||||
ansible.builtin.template:
|
||||
src: snmpd.conf.j2
|
||||
dest: /etc/snmp/snmpd.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
notify: Redémarrer snmpd
|
||||
|
||||
- name: Créer les utilisateurs SNMPv3
|
||||
ansible.builtin.include_tasks: v3.yml
|
||||
when: snmp_v3_users | length > 0
|
||||
|
||||
- name: Activer et démarrer snmpd
|
||||
ansible.builtin.systemd:
|
||||
name: snmpd
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
# createUser n'est lu qu'au démarrage : le service est arrêté, la ligne est
|
||||
# ajoutée à la base persistante, puis snmpd la convertit en clés au redémarrage.
|
||||
- name: Vérifier quels utilisateurs SNMPv3 existent déjà
|
||||
ansible.builtin.shell:
|
||||
cmd: >-
|
||||
set -o pipefail;
|
||||
hex=$(printf '%s' '{{ item.name }}' | od -An -tx1 | tr -d ' \n');
|
||||
grep -c -e "createUser {{ item.name }} " -e "usmUser .*0x${hex} " {{ snmp_persistent_conf[ansible_facts['os_family']] }}
|
||||
executable: /bin/bash
|
||||
loop: "{{ snmp_v3_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: snmp_v3_existing
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Arrêter snmpd avant la création des utilisateurs
|
||||
ansible.builtin.systemd:
|
||||
name: snmpd
|
||||
state: stopped
|
||||
when: snmp_v3_existing.results | selectattr('rc', 'ne', 0) | list | length > 0
|
||||
|
||||
- name: Déclarer les utilisateurs SNMPv3 manquants
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ snmp_persistent_conf[ansible_facts['os_family']] }}"
|
||||
line: >-
|
||||
createUser {{ item.item.name }} {{ item.item.auth | default('SHA') }} "{{ item.item.auth_pass }}"
|
||||
{{ item.item.priv | default('AES') }} "{{ item.item.priv_pass }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
loop: "{{ snmp_v3_existing.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
when: item.rc != 0
|
||||
no_log: true
|
||||
notify: Redémarrer snmpd
|
||||
@@ -0,0 +1,26 @@
|
||||
# {{ ansible_managed }}
|
||||
agentaddress {{ snmp_listen }}
|
||||
sysLocation {{ snmp_sys_location }}
|
||||
sysContact {{ snmp_sys_contact }}
|
||||
sysServices 72
|
||||
|
||||
# Lecture seule, arbre complet.
|
||||
view systemonly included .1
|
||||
|
||||
{% for c in snmp_communities %}
|
||||
{% if c.sources | default([]) | length == 0 %}
|
||||
rocommunity {{ c.name }} default -V systemonly
|
||||
{% else %}
|
||||
{% for src in c.sources %}
|
||||
rocommunity{{ '6' if ':' in src else '' }} {{ c.name }} {{ src }} -V systemonly
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for u in snmp_v3_users %}
|
||||
rouser {{ u.name }} priv -V systemonly
|
||||
{% endfor %}
|
||||
|
||||
{% for e in snmp_extends %}
|
||||
extend {{ e.name }} {{ e.command }}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
snmp_packages:
|
||||
Debian: [snmpd, snmp]
|
||||
RedHat: [net-snmp, net-snmp-utils]
|
||||
snmp_persistent_conf:
|
||||
Debian: /var/lib/snmp/snmpd.conf
|
||||
RedHat: /var/lib/net-snmp/snmpd.conf
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
# Rôle ssh — reprend 01-ssh (sshd_config, ssh_config, banner, motd, clés
|
||||
# autorisées) et le durcissement du projet terraform-proxmox-serveur-debian-12.
|
||||
#
|
||||
# Principe : les accès distants sont exclusivement en SSH par clé publique ;
|
||||
# le mot de passe des comptes reste utilisable en local (console, su),
|
||||
# le rôle ne le verrouille jamais.
|
||||
|
||||
ssh_port: 22
|
||||
# yes | prohibit-password | no
|
||||
# prohibit-password : root par clé uniquement (nécessaire si Ansible se
|
||||
# connecte en root) ; le mot de passe root reste utilisable sur la console.
|
||||
ssh_permit_root_login: prohibit-password
|
||||
|
||||
# Comptes autorisés en SSH. Chaque entrée : name (obligatoire), groups (liste,
|
||||
# « sudo_or_wheel » est traduit selon l'OS), sudo_nopasswd (bool), shell,
|
||||
# password_hash (optionnel, pour un mot de passe local — jamais en clair).
|
||||
# Les clés publiques sont lues dans files/keys/<name>/*.pub.
|
||||
ssh_users:
|
||||
- name: root
|
||||
|
||||
ssh_keys_dir: "{{ playbook_dir }}/files/keys"
|
||||
|
||||
# Clés centralisées dans /etc/ssh/authorized_keys/<utilisateur>, appartenant à
|
||||
# root : un utilisateur ne peut pas ajouter de clé lui-même (reprise de
|
||||
# l'ancien script, qui posait un lien symbolique vers /root/.ssh).
|
||||
# false : clés classiques dans ~/.ssh/authorized_keys.
|
||||
ssh_central_authorized_keys: true
|
||||
|
||||
# Groupes supplémentaires autorisés en SSH (en plus des ssh_users).
|
||||
ssh_allow_groups: []
|
||||
|
||||
# Durcissement du démon.
|
||||
ssh_max_auth_tries: 3
|
||||
ssh_max_sessions: 4
|
||||
ssh_max_startups: "10:30:60"
|
||||
ssh_login_grace_time: 30
|
||||
ssh_client_alive_interval: 300
|
||||
ssh_client_alive_count_max: 2
|
||||
ssh_x11_forwarding: false
|
||||
ssh_agent_forwarding: false
|
||||
ssh_tcp_forwarding: false
|
||||
ssh_log_level: VERBOSE
|
||||
|
||||
# Algorithmes (Debian/Ubuntu uniquement : sous Red Hat ils sont imposés par
|
||||
# crypto-policies, ne pas les redéfinir dans sshd_config).
|
||||
ssh_kex_algorithms:
|
||||
- [email protected]
|
||||
- curve25519-sha256
|
||||
- [email protected]
|
||||
- diffie-hellman-group16-sha512
|
||||
- diffie-hellman-group18-sha512
|
||||
ssh_ciphers:
|
||||
- [email protected]
|
||||
- [email protected]
|
||||
- [email protected]
|
||||
- aes256-ctr
|
||||
ssh_macs:
|
||||
- [email protected]
|
||||
- [email protected]
|
||||
- [email protected]
|
||||
ssh_host_key_algorithms:
|
||||
- ssh-ed25519
|
||||
- [email protected]
|
||||
- rsa-sha2-512
|
||||
- rsa-sha2-256
|
||||
ssh_pubkey_accepted_algorithms: "{{ ssh_host_key_algorithms }}"
|
||||
|
||||
# Bannière d'avertissement (avant authentification) et MOTD (après).
|
||||
ssh_banner: true
|
||||
ssh_banner_organisation: "l'organisation"
|
||||
# Logo ASCII affiché en tête de bannière (chaîne multiligne, vide = pas de logo).
|
||||
ssh_banner_logo: ""
|
||||
ssh_motd: true
|
||||
# Sous Ubuntu : désactiver les MOTD publicitaires (motd-news, help-text).
|
||||
ssh_motd_disable_ubuntu_extras: true
|
||||
|
||||
# Durcissement du client ssh (ssh_config) : pas d'agent forwarding ni de X11
|
||||
# par défaut, known_hosts hachés.
|
||||
ssh_client_hardening: true
|
||||
|
||||
# Après redémarrage du démon, ouvrir une NOUVELLE connexion depuis le
|
||||
# contrôleur pour vérifier qu'on ne s'est pas enfermé dehors.
|
||||
ssh_verify_new_connection: true
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Géré par Ansible (rôle ssh) — repris de 01-ssh/00-basic.
|
||||
# get load averages
|
||||
IFS=" " read LOAD1 LOAD5 LOAD15 <<<$(awk '{ print $1,$2,$3 }' /proc/loadavg)
|
||||
# get free memory
|
||||
IFS=" " read USED AVAIL TOTAL <<<$(free -htm | awk '/Mem/ { print $3,$7,$2 }')
|
||||
# get processes
|
||||
PROCESS=$(ps -eo user=|sort|uniq -c | awk '{ print $2 " " $1 }')
|
||||
PROCESS_ALL=$(echo "$PROCESS"| awk {'print $2'} | awk '{ SUM += $1} END { print SUM }')
|
||||
PROCESS_ROOT=$(echo "$PROCESS" | awk '/root/ { print $2}')
|
||||
PROCESS_USER=$(echo "$PROCESS" | awk '!/root/ { SUM += $2} END { print SUM }')
|
||||
# get processors
|
||||
PROCESSOR_NAME=$(awk -F": " '/model name/ { print $2 }' /proc/cpuinfo | head -1)
|
||||
PROCESSOR_COUNT=$(grep -ioPc 'processor\t:' /proc/cpuinfo)
|
||||
|
||||
# colors
|
||||
W="\e[0;39m"
|
||||
G="\e[1;32m"
|
||||
R="\e[1;31m"
|
||||
dim="\e[2m"
|
||||
undim="\e[0m"
|
||||
|
||||
echo -e "${W}System info:
|
||||
$W Hostname$dim····$undim: $W${HOSTNAME}
|
||||
$W Distro$dim······$undim: $W$(. /etc/os-release; echo "$PRETTY_NAME")
|
||||
$W Kernel$dim······$undim: $W$(uname -sr)
|
||||
$W Uptime$dim······$undim: $W$(uptime -p)
|
||||
$W Load$dim········$undim: $G$LOAD1$W (1m), $G$LOAD5$W (5m), $G$LOAD15$W (15m)
|
||||
$W Processes$dim···$undim: $G$PROCESS_ROOT$W (root), $G$PROCESS_USER$W (user), $G$PROCESS_ALL$W (total)
|
||||
$W CPU$dim·········$undim: $W$PROCESSOR_NAME ($G$PROCESSOR_COUNT$W vCPU)
|
||||
$W Memory$dim······$undim: $G$USED$W used, $G$AVAIL$W avail, $G$TOTAL$W total"
|
||||
|
||||
# config
|
||||
max_usage=90
|
||||
bar_width=50
|
||||
|
||||
# disk usage: ignore zfs, squashfs & tmpfs
|
||||
printf "\nDisk usage:\n"
|
||||
|
||||
while read line; do
|
||||
# get disk usage
|
||||
usage=$(echo "$line" | awk '{print $2}' | sed 's/%//')
|
||||
used_width=$((($usage*$bar_width)/100))
|
||||
# color is green if usage < max_usage, else red
|
||||
if [ "${usage}" -ge "${max_usage}" ]; then
|
||||
color=$R
|
||||
else
|
||||
color=$G
|
||||
fi
|
||||
# print green/red bar until used_width
|
||||
bar="[${color}"
|
||||
for ((i=0; i<$used_width; i++)); do
|
||||
bar+="="
|
||||
done
|
||||
# print dimmmed bar until end
|
||||
bar+="${W}${dim}"
|
||||
for ((i=$used_width; i<$bar_width; i++)); do
|
||||
bar+="·"
|
||||
done
|
||||
bar+="${undim}]"
|
||||
# print usage line & bar
|
||||
echo "${line}" | awk '{ printf("%-31s%+3s used out of %+4s\n", $1, $2, $3); }' | sed -e 's/^/ /'
|
||||
echo -e "${bar}" | sed -e 's/^/ /'
|
||||
done < <(df -H -x zfs -x squashfs -x tmpfs -x devtmpfs -x overlay -x nfs -x nfs4 -x cifs --output=target,pcent,size | tail -n+2)
|
||||
|
||||
printf "\n"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Redémarrer sshd
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ ssh_service_name[ansible_facts['os_family']] }}"
|
||||
state: restarted
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
- name: Déposer la bannière d'avertissement (SSH et console)
|
||||
ansible.builtin.template:
|
||||
src: banner.j2
|
||||
dest: "{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- /etc/issue.net
|
||||
- /etc/issue
|
||||
when: ssh_banner | bool
|
||||
|
||||
- name: "Déposer le script MOTD (Debian/Ubuntu : update-motd.d)"
|
||||
ansible.builtin.copy:
|
||||
src: 00-basic
|
||||
dest: /etc/update-motd.d/00-basic
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when:
|
||||
- ssh_motd | bool
|
||||
- ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Désactiver les MOTD annexes d'Ubuntu
|
||||
ansible.builtin.file:
|
||||
path: "/etc/update-motd.d/{{ item }}"
|
||||
mode: "0644"
|
||||
loop:
|
||||
- 10-help-text
|
||||
- 50-motd-news
|
||||
- 50-landscape-sysinfo
|
||||
- 88-esm-announce
|
||||
- 91-contract-ua-esm-status
|
||||
failed_when: false
|
||||
when:
|
||||
- ssh_motd | bool
|
||||
- ssh_motd_disable_ubuntu_extras | bool
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
|
||||
- name: Désactiver motd-news (Ubuntu)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/default/motd-news
|
||||
regexp: '^ENABLED='
|
||||
line: "ENABLED=0"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- ssh_motd | bool
|
||||
- ssh_motd_disable_ubuntu_extras | bool
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
|
||||
- name: "Déposer le script MOTD (Red Hat : profile.d)"
|
||||
ansible.builtin.copy:
|
||||
src: 00-basic
|
||||
dest: /usr/local/bin/motd-basic
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when:
|
||||
- ssh_motd | bool
|
||||
- ansible_facts['os_family'] == 'RedHat'
|
||||
|
||||
- name: Appeler le MOTD à l'ouverture d'un shell interactif (Red Hat)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/profile.d/zz-motd.sh
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
[ -n "$PS1" ] && [ -x /usr/local/bin/motd-basic ] && /usr/local/bin/motd-basic
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- ssh_motd | bool
|
||||
- ansible_facts['os_family'] == 'RedHat'
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
- name: Créer le répertoire central des clés autorisées
|
||||
ansible.builtin.file:
|
||||
path: "{{ ssh_central_keys_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Écrire le fichier de clés de chaque compte
|
||||
ansible.builtin.template:
|
||||
src: authorized_keys.j2
|
||||
dest: "{{ ssh_central_keys_dir }}/{{ item.name }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: ssh_users_keys[item.name] | length > 0
|
||||
|
||||
- name: Supprimer le fichier central d'un compte sans clé
|
||||
ansible.builtin.file:
|
||||
path: "{{ ssh_central_keys_dir }}/{{ item.name }}"
|
||||
state: absent
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: ssh_users_keys[item.name] | length == 0
|
||||
|
||||
- name: Lister les fichiers de clés d'anciens comptes
|
||||
ansible.builtin.find:
|
||||
paths: "{{ ssh_central_keys_dir }}"
|
||||
file_type: file
|
||||
register: ssh_central_existing
|
||||
|
||||
- name: Supprimer les fichiers de clés des comptes qui ne sont plus gérés
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ ssh_central_existing.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
when: item.path | basename not in ssh_allow_users_effective
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: Installer le serveur OpenSSH
|
||||
ansible.builtin.package:
|
||||
name: openssh-server
|
||||
state: present
|
||||
|
||||
- name: Préparer les comptes et leurs clés
|
||||
ansible.builtin.import_tasks: users.yml
|
||||
|
||||
- name: Configurer le démon sshd
|
||||
ansible.builtin.import_tasks: sshd.yml
|
||||
|
||||
- name: Déployer la bannière et le MOTD
|
||||
ansible.builtin.import_tasks: banner_motd.yml
|
||||
|
||||
- name: Durcir le client ssh
|
||||
ansible.builtin.template:
|
||||
src: ssh_config.j2
|
||||
dest: "{{ ssh_client_dropin_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ssh_client_hardening | bool
|
||||
|
||||
- name: Appliquer la configuration sshd maintenant
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Vérifier qu'une nouvelle connexion SSH aboutit
|
||||
ansible.builtin.import_tasks: verify.yml
|
||||
when: ssh_verify_new_connection | bool
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
- name: Lire la version du paquet openssh-server
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Extraire la version d'OpenSSH
|
||||
ansible.builtin.set_fact:
|
||||
ssh_version: "{{ ansible_facts['packages']['openssh-server'][0].version | regex_search('([0-9]+\\.[0-9]+)') }}"
|
||||
|
||||
- name: Déterminer si sshd prend en charge Include (OpenSSH >= 8.2)
|
||||
ansible.builtin.set_fact:
|
||||
ssh_supports_include: "{{ ssh_version is version('8.2', '>=') }}"
|
||||
|
||||
- name: Sauvegarder le sshd_config d'origine une seule fois
|
||||
ansible.builtin.copy:
|
||||
src: /etc/ssh/sshd_config
|
||||
dest: /etc/ssh/sshd_config.avant-ansible
|
||||
remote_src: true
|
||||
force: false
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
|
||||
- name: Appliquer le durcissement sshd
|
||||
block:
|
||||
- name: Créer le répertoire des drop-ins sshd
|
||||
ansible.builtin.file:
|
||||
path: "{{ ssh_dropin_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: ssh_supports_include | bool
|
||||
|
||||
- name: S'assurer que sshd_config inclut le répertoire des drop-ins
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf'
|
||||
line: "Include /etc/ssh/sshd_config.d/*.conf"
|
||||
insertbefore: BOF
|
||||
when: ssh_supports_include | bool
|
||||
notify: Redémarrer sshd
|
||||
|
||||
- name: Déposer le drop-in de durcissement
|
||||
ansible.builtin.template:
|
||||
src: sshd_durcissement.conf.j2
|
||||
dest: "{{ ssh_dropin_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
when: ssh_supports_include | bool
|
||||
notify: Redémarrer sshd
|
||||
|
||||
- name: Insérer le durcissement en tête de sshd_config (OpenSSH < 8.2)
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
block: "{{ lookup('ansible.builtin.template', 'sshd_durcissement.conf.j2') }}"
|
||||
marker: "# {mark} DURCISSEMENT ANSIBLE"
|
||||
insertbefore: BOF
|
||||
when: not ssh_supports_include | bool
|
||||
notify: Redémarrer sshd
|
||||
|
||||
- name: Neutraliser dans sshd_config les directives qui contrediraient le durcissement
|
||||
ansible.builtin.replace:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^(\s*(?:PermitRootLogin|PasswordAuthentication|AuthorizedKeysFile|X11Forwarding|Banner|PrintMotd)\s.*)$'
|
||||
replace: '#\1'
|
||||
after: "# END DURCISSEMENT ANSIBLE"
|
||||
when: not ssh_supports_include | bool
|
||||
notify: Redémarrer sshd
|
||||
|
||||
- name: Valider la configuration sshd
|
||||
ansible.builtin.command:
|
||||
cmd: sshd -t
|
||||
changed_when: false
|
||||
rescue:
|
||||
- name: Retirer le drop-in invalide
|
||||
ansible.builtin.file:
|
||||
path: "{{ ssh_dropin_file }}"
|
||||
state: absent
|
||||
when: ssh_supports_include | bool
|
||||
|
||||
- name: Restaurer sshd_config
|
||||
ansible.builtin.copy:
|
||||
src: /etc/ssh/sshd_config.avant-ansible
|
||||
dest: /etc/ssh/sshd_config
|
||||
remote_src: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
when: not ssh_supports_include | bool
|
||||
|
||||
- name: Arrêter sur configuration sshd invalide
|
||||
ansible.builtin.fail:
|
||||
msg: "sshd -t a rejeté la configuration : elle a été retirée, le démon n'a pas été redémarré."
|
||||
|
||||
- name: Autoriser le port SSH dans SELinux
|
||||
community.general.seport:
|
||||
ports: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
setype: ssh_port_t
|
||||
state: present
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'RedHat'
|
||||
- ansible_facts['selinux'].status | default('disabled') == 'enabled'
|
||||
- ssh_port | int != 22
|
||||
|
||||
- name: Désactiver l'activation par socket (Ubuntu >= 22.10)
|
||||
ansible.builtin.systemd:
|
||||
name: ssh.socket
|
||||
enabled: false
|
||||
state: stopped
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution_version'] is version('22.10', '>=')
|
||||
notify: Redémarrer sshd
|
||||
|
||||
- name: Lister les clés privées d'hôte
|
||||
ansible.builtin.find:
|
||||
paths: /etc/ssh
|
||||
patterns: "ssh_host_*_key"
|
||||
register: ssh_host_keys
|
||||
|
||||
- name: Restreindre les droits des clés privées d'hôte
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
loop: "{{ ssh_host_keys.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
|
||||
- name: Activer et démarrer sshd
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ ssh_service_name[ansible_facts['os_family']] }}"
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
- name: Recenser les clés publiques disponibles par compte
|
||||
ansible.builtin.set_fact:
|
||||
ssh_users_keys: >-
|
||||
{{ ssh_users_keys | default({}) | combine({
|
||||
item.name: lookup('ansible.builtin.fileglob', ssh_keys_dir ~ '/' ~ item.name ~ '/*.pub', wantlist=True)
|
||||
}) }}
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Calculer la liste AllowUsers
|
||||
ansible.builtin.set_fact:
|
||||
ssh_allow_users_effective: "{{ ssh_users | map(attribute='name') | list }}"
|
||||
|
||||
- name: Refuser une configuration qui enfermerait Ansible dehors
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ssh_users | length > 0
|
||||
- (ssh_users_keys | default({})).values() | map('length') | sum > 0
|
||||
- ssh_ansible_login in ssh_allow_users_effective or ssh_allow_groups | length > 0
|
||||
- ssh_users_keys[ssh_ansible_login] | default([]) | length > 0 or ssh_allow_groups | length > 0
|
||||
- not (ssh_ansible_login == 'root' and ssh_permit_root_login == 'no')
|
||||
fail_msg: >-
|
||||
Le compte de connexion Ansible « {{ ssh_ansible_login }} » doit figurer dans ssh_users
|
||||
avec au moins une clé dans {{ ssh_keys_dir }}/{{ ssh_ansible_login }}/*.pub,
|
||||
et ssh_permit_root_login ne peut pas valoir « no » si Ansible se connecte en root.
|
||||
vars:
|
||||
ssh_ansible_login: "{{ ansible_user | default('root') }}"
|
||||
|
||||
- name: Créer les comptes (hors root)
|
||||
ansible.builtin.user:
|
||||
name: "{{ item.name }}"
|
||||
shell: "{{ item.shell | default('/bin/bash') }}"
|
||||
groups: "{{ item.groups | default([]) | map('replace', 'sudo_or_wheel', common_sudo_group_effective) | list }}"
|
||||
append: true
|
||||
password: "{{ item.password_hash | default(omit) }}"
|
||||
create_home: true
|
||||
state: present
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: item.name != 'root'
|
||||
no_log: true
|
||||
|
||||
- name: Autoriser sudo sans mot de passe pour les comptes de service
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/90-{{ item.name }}"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
{{ item.name }} ALL=(ALL) NOPASSWD:ALL
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when:
|
||||
- item.name != 'root'
|
||||
- item.sudo_nopasswd | default(false) | bool
|
||||
|
||||
- name: Retirer le sudo sans mot de passe des comptes qui ne le demandent plus
|
||||
ansible.builtin.file:
|
||||
path: "/etc/sudoers.d/90-{{ item.name }}"
|
||||
state: absent
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when:
|
||||
- item.name != 'root'
|
||||
- not (item.sudo_nopasswd | default(false) | bool)
|
||||
|
||||
- name: Déployer les clés autorisées (répertoire central)
|
||||
ansible.builtin.import_tasks: keys_central.yml
|
||||
when: ssh_central_authorized_keys | bool
|
||||
|
||||
- name: Déployer les clés autorisées (~/.ssh)
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ item.name }}"
|
||||
key: "{{ ssh_users_keys[item.name] | map('ansible.builtin.file') | join('\n') }}"
|
||||
exclusive: true
|
||||
manage_dir: true
|
||||
loop: "{{ ssh_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when:
|
||||
- not ssh_central_authorized_keys | bool
|
||||
- ssh_users_keys[item.name] | length > 0
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Construire la commande de vérification
|
||||
ansible.builtin.set_fact:
|
||||
ssh_verify_argv: >-
|
||||
{{ ['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=15', '-o', 'StrictHostKeyChecking=accept-new',
|
||||
'-o', 'ControlPath=none', '-p', ssh_port | string]
|
||||
+ (['-i', ansible_ssh_private_key_file] if ansible_ssh_private_key_file is defined else [])
|
||||
+ [(ansible_user | default('root')) ~ '@' ~ (ansible_host | default(inventory_hostname)), 'true'] }}
|
||||
|
||||
- name: Ouvrir une nouvelle session SSH depuis le contrôleur
|
||||
ansible.builtin.command:
|
||||
argv: "{{ ssh_verify_argv }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: ssh_verify
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Signaler un échec de connexion après durcissement
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Une NOUVELLE connexion SSH vers {{ inventory_hostname }} échoue (code {{ ssh_verify.rc }}).
|
||||
La session courante reste ouverte : vérifier {{ ssh_dropin_file }} et
|
||||
{{ ssh_central_keys_dir if ssh_central_authorized_keys else '~/.ssh/authorized_keys' }}
|
||||
avant de la fermer.
|
||||
when: ssh_verify.rc != 0
|
||||
@@ -0,0 +1,5 @@
|
||||
# {{ ansible_managed }}
|
||||
# Source : files/keys/{{ item.name }}/*.pub
|
||||
{% for key_file in ssh_users_keys[item.name] | sort %}
|
||||
{{ lookup('ansible.builtin.file', key_file) | trim }}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
{% if ssh_banner_logo | length > 0 %}
|
||||
{{ ssh_banner_logo | regex_replace('\\s+$', '') }}
|
||||
|
||||
{% endif %}
|
||||
AVERTISSEMENT : L'accès à ce système est réservé aux utilisateurs
|
||||
dûment autorisés.
|
||||
|
||||
Toute tentative d'accès à ce système sans autorisation, ou de maintien
|
||||
frauduleux dans ce système, fera l'objet de poursuites conformément à
|
||||
la politique de {{ ssh_banner_organisation }}.
|
||||
|
||||
Tout utilisateur autorisé est informé et reconnaît que ses actions
|
||||
peuvent être enregistrées, conservées et auditées conformément aux
|
||||
chartes et politiques internes de {{ ssh_banner_organisation }}.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# {{ ansible_managed }}
|
||||
Host *
|
||||
ForwardAgent no
|
||||
ForwardX11 no
|
||||
HashKnownHosts yes
|
||||
GSSAPIAuthentication no
|
||||
StrictHostKeyChecking ask
|
||||
SendEnv LANG LC_*
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
@@ -0,0 +1,62 @@
|
||||
# {{ ansible_managed }}
|
||||
# Première valeur lue = valeur retenue : ce fichier est lu avant sshd_config.
|
||||
|
||||
Port {{ ssh_port }}
|
||||
LogLevel {{ ssh_log_level }}
|
||||
|
||||
# Authentification : clé publique uniquement, jamais de mot de passe à distance.
|
||||
PermitRootLogin {{ ssh_permit_root_login }}
|
||||
PubkeyAuthentication yes
|
||||
AuthenticationMethods publickey
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
HostbasedAuthentication no
|
||||
IgnoreRhosts yes
|
||||
StrictModes yes
|
||||
UsePAM yes
|
||||
{% if ssh_central_authorized_keys %}
|
||||
AuthorizedKeysFile {{ ssh_central_keys_dir }}/%u
|
||||
{% else %}
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
{% endif %}
|
||||
AllowUsers {{ ssh_allow_users_effective | join(' ') }}
|
||||
{% if ssh_allow_groups | length > 0 %}
|
||||
AllowGroups {{ ssh_allow_groups | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
# Limites anti-force-brute et sessions inactives.
|
||||
MaxAuthTries {{ ssh_max_auth_tries }}
|
||||
MaxSessions {{ ssh_max_sessions }}
|
||||
MaxStartups {{ ssh_max_startups }}
|
||||
LoginGraceTime {{ ssh_login_grace_time }}
|
||||
ClientAliveInterval {{ ssh_client_alive_interval }}
|
||||
ClientAliveCountMax {{ ssh_client_alive_count_max }}
|
||||
|
||||
# Surface réduite.
|
||||
X11Forwarding {{ 'yes' if ssh_x11_forwarding else 'no' }}
|
||||
AllowAgentForwarding {{ 'yes' if ssh_agent_forwarding else 'no' }}
|
||||
AllowTcpForwarding {{ 'yes' if ssh_tcp_forwarding else 'no' }}
|
||||
PermitTunnel no
|
||||
GatewayPorts no
|
||||
PermitUserEnvironment no
|
||||
UseDNS no
|
||||
{% if ansible_facts['os_family'] == 'Debian' %}
|
||||
DebianBanner no
|
||||
|
||||
# Algorithmes (Red Hat : gérés par crypto-policies).
|
||||
KexAlgorithms {{ ssh_kex_algorithms | join(',') }}
|
||||
Ciphers {{ ssh_ciphers | join(',') }}
|
||||
MACs {{ ssh_macs | join(',') }}
|
||||
HostKeyAlgorithms {{ ssh_host_key_algorithms | join(',') }}
|
||||
PubkeyAcceptedAlgorithms {{ ssh_pubkey_accepted_algorithms | join(',') }}
|
||||
{% endif %}
|
||||
|
||||
# Affichage.
|
||||
{% if ssh_banner %}
|
||||
Banner /etc/issue.net
|
||||
{% else %}
|
||||
Banner none
|
||||
{% endif %}
|
||||
PrintMotd no
|
||||
PrintLastLog yes
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
ssh_service_name:
|
||||
Debian: ssh
|
||||
RedHat: sshd
|
||||
ssh_dropin_dir: /etc/ssh/sshd_config.d
|
||||
# « 00- » : les fichiers inclus sont lus par ordre lexical et la PREMIÈRE
|
||||
# valeur rencontrée l'emporte. Nos réglages passent donc avant
|
||||
# 50-cloud-init.conf (Ubuntu) et 50-redhat.conf (Red Hat).
|
||||
ssh_dropin_file: "{{ ssh_dropin_dir }}/00-durcissement.conf"
|
||||
ssh_client_dropin_file: /etc/ssh/ssh_config.d/00-durcissement.conf
|
||||
ssh_central_keys_dir: /etc/ssh/authorized_keys
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Rôle updates — reprend 02-apt (update + full-upgrade) et ajoute les mises à
|
||||
# jour de sécurité automatiques (unattended-upgrades / dnf-automatic).
|
||||
updates_upgrade_now: true
|
||||
updates_automatic: true
|
||||
# Redémarrage automatique en fin de passage si le noyau l'exige.
|
||||
# false par défaut : le résumé de fin de playbook signale le besoin.
|
||||
updates_reboot_if_required: false
|
||||
# Heure des mises à jour automatiques (dnf-automatic uniquement, format OnCalendar).
|
||||
updates_automatic_schedule: "*-*-* 04:00"
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: Recharger systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
- name: Mettre à jour tous les paquets (full-upgrade)
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
upgrade: full
|
||||
autoremove: true
|
||||
autoclean: true
|
||||
when: updates_upgrade_now | bool
|
||||
|
||||
- name: Installer unattended-upgrades
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- unattended-upgrades
|
||||
- apt-listchanges
|
||||
state: present
|
||||
when: updates_automatic | bool
|
||||
|
||||
- name: Activer les mises à jour automatiques
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
content: |
|
||||
// {{ ansible_managed }}
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: updates_automatic | bool
|
||||
|
||||
- name: Limiter unattended-upgrades aux mises à jour de sécurité
|
||||
ansible.builtin.template:
|
||||
src: 52unattended-upgrades-local.j2
|
||||
dest: /etc/apt/apt.conf.d/52unattended-upgrades-local
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: updates_automatic | bool
|
||||
|
||||
- name: Activer le service unattended-upgrades
|
||||
ansible.builtin.systemd:
|
||||
name: unattended-upgrades
|
||||
enabled: true
|
||||
state: started
|
||||
when: updates_automatic | bool
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: Mettre à jour le système
|
||||
ansible.builtin.include_tasks: "{{ ansible_facts['os_family'] | lower }}.yml"
|
||||
|
||||
- name: Déterminer si un redémarrage est nécessaire (Debian)
|
||||
ansible.builtin.stat:
|
||||
path: /var/run/reboot-required
|
||||
register: updates_reboot_file
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Déterminer si un redémarrage est nécessaire (Red Hat)
|
||||
ansible.builtin.command:
|
||||
cmd: dnf needs-restarting -r
|
||||
register: updates_needs_restarting
|
||||
changed_when: false
|
||||
failed_when: updates_needs_restarting.rc not in [0, 1]
|
||||
when: ansible_facts['os_family'] == 'RedHat'
|
||||
|
||||
- name: Exposer l'état de redémarrage
|
||||
ansible.builtin.set_fact:
|
||||
updates_reboot_required: >-
|
||||
{{ (updates_reboot_file.stat.exists | default(false))
|
||||
or (updates_needs_restarting.rc | default(0) == 1) }}
|
||||
|
||||
- name: Redémarrer le serveur
|
||||
ansible.builtin.reboot:
|
||||
msg: "Redémarrage planifié par Ansible (mises à jour)"
|
||||
reboot_timeout: 600
|
||||
when:
|
||||
- updates_reboot_if_required | bool
|
||||
- updates_reboot_required | bool
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
- name: Mettre à jour tous les paquets # noqa: package-latest — mise à jour complète voulue
|
||||
ansible.builtin.dnf:
|
||||
name: "*"
|
||||
state: latest
|
||||
update_cache: true
|
||||
when: updates_upgrade_now | bool
|
||||
|
||||
- name: Installer dnf-automatic
|
||||
ansible.builtin.dnf:
|
||||
name: dnf-automatic
|
||||
state: present
|
||||
when: updates_automatic | bool
|
||||
|
||||
- name: Limiter dnf-automatic aux mises à jour de sécurité
|
||||
community.general.ini_file:
|
||||
path: /etc/dnf/automatic.conf
|
||||
section: "{{ item.section }}"
|
||||
option: "{{ item.option }}"
|
||||
value: "{{ item.value }}"
|
||||
no_extra_spaces: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- { section: commands, option: upgrade_type, value: security }
|
||||
- { section: commands, option: apply_updates, value: "yes" }
|
||||
- { section: commands, option: download_updates, value: "yes" }
|
||||
- { section: emitters, option: emit_via, value: stdio }
|
||||
loop_control:
|
||||
label: "{{ item.option }}"
|
||||
when: updates_automatic | bool
|
||||
|
||||
- name: Créer le répertoire de surcharge du timer
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/dnf-automatic.timer.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: updates_automatic | bool
|
||||
|
||||
|
||||
- name: Planifier dnf-automatic
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/dnf-automatic.timer.d/override.conf
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
[Timer]
|
||||
OnCalendar=
|
||||
OnCalendar={{ updates_automatic_schedule }}
|
||||
RandomizedDelaySec=30m
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: updates_automatic | bool
|
||||
notify: Recharger systemd
|
||||
|
||||
- name: Activer le timer dnf-automatic
|
||||
ansible.builtin.systemd:
|
||||
name: dnf-automatic.timer
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
when: updates_automatic | bool
|
||||
@@ -0,0 +1,14 @@
|
||||
// {{ ansible_managed }}
|
||||
Unattended-Upgrade::Origins-Pattern {
|
||||
{% if ansible_facts['distribution'] == 'Ubuntu' %}
|
||||
"origin=Ubuntu,archive=${distro_codename}-security";
|
||||
"origin=UbuntuESMApps,archive=${distro_codename}-apps-security";
|
||||
"origin=UbuntuESM,archive=${distro_codename}-infra-security";
|
||||
{% else %}
|
||||
"origin=Debian,codename=${distro_codename},label=Debian-Security";
|
||||
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
|
||||
{% endif %}
|
||||
};
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Reference in new issue
Block a user