Configuration de base des VM (baseline)

Rôle

Deux dépôts complémentaires appliquent et maintiennent la configuration de base de la flotte de VM Indio, à deux niveaux différents :

  • /root/baseline (Ansible, indio/iac/baseline) : gestion des comptes (rotation du mot de passe root/indio-adm), déploiement/maintenance du dépôt interne sur des VM existantes, agents de supervision et d'inventaire (Zabbix, OCS), relais des logs (rsyslog → syslog-ng, plugin d'audit).
  • /root/indio-baseline-config (paquet RPM noarch, indio/iac/indio-baseline-config) : paquet consolidé, installé directement par le template doré Packer (tmpl-rocky96-hardened), qui pousse en une fois les dépôts internes (Nexus), le client NTP interne, le proxy Squid par défaut et la confiance de la CA interne Vault PKI.

Ces deux projets se recouvrent partiellement dans le temps : le RPM a été créé pour consolider ce que /root/baseline/ansible/repo.yml faisait déjà partiellement (dépôt interne, en version réduite à 3 sections) et pour combler des manques qui n'existaient dans aucun des deux avant (NTP, proxy actif par défaut, CA interne).

Architecture

/root/baseline (Ansible)

baseline/ansible/
├── ansible.cfg           # inventory.ini, remote_user indio-adm, become=sudo, vault_password_file
├── inventory.ini         # groupe [fleet] : VM existantes gérées par ce dépôt (nom IND<SEG><NNN> + IP admin)
├── group_vars/fleet.yml  # variables communes (Zabbix HA, OCS, proxy Squid, syslog central...)
├── set-passwords.yml     # rotation mdp root/indio-adm
├── repo.yml              # dépôt interne (indio.repo à 3 sections)
├── agents.yml            # agents Zabbix + OCS sur toute la flotte
├── syslog-forward.yml    # drop-in rsyslog -> syslog-ng central
├── audit-syslog.yml      # relais auditd -> syslog (plugin audisp-syslog)
├── reboot-batch.yml      # reboot séquentiel par lot (serial 1)
├── files/                # indio.repo, drop-in rsyslog
└── roles/
    ├── ocs_agent/                  # agent OCS Inventory NG (compilé depuis les sources)
    ├── zabbix_agent/               # agent Zabbix classique
    ├── zabbix_agent2_mysql/        # bascule agent2 + plugin MySQL natif
    └── zabbix_agent2_postgresql/   # bascule agent2 + plugin PostgreSQL natif

group_vars/fleet.yml contient notamment l'IP du cluster Zabbix HA, le FQDN/IP du serveur OCS, la VIP du proxy Squid, le FQDN/port du serveur syslog central, ainsi qu'une variable chiffrée par ansible-vault (mysql_zbx_password, déchiffrée via vault_password_file). Aucun rôle dédié au hardening SSH n'existe dans ce dépôt (voir Points d'attention).

/root/indio-baseline-config (RPM)

indio-baseline-config/
├── indio-baseline-config.spec   # sources statiques + scriptlets %post/%postun idempotents
├── files/
│   ├── indio.repo                # 6 sections : baseos/appstream/extras/crb/epel/custom (Nexus)
│   ├── indio-proxy.sh             # /etc/profile.d, proxy pour les shells interactifs
│   ├── 10-indio-proxy.conf        # /etc/systemd/system.conf.d, DefaultEnvironment (tous services)
│   ├── indio-ntp.conf             # /etc/chrony.d, 3 serveurs NTP internes
│   ├── indio-root-ca.crt / indio-intermediate-ca.crt   # CA Vault PKI, /etc/pki/ca-trust
│   └── RPM-GPG-KEY-EPEL-9
├── dist/                         # RPM déjà construits : 1.0.2, 1.0.3, 1.0.4
└── README.md

Versions successives (voir %changelog du spec) : 1.0.0 (initiale : dépôts Nexus/EPEL, NTP, proxy, CA) → 1.0.1 (ajout section indio-custom) → 1.0.2 (fix d'un typo de domaine sur indio-epel + ajout de proxy=_none_ sur chaque section indio-*, faute de quoi le proxy global casse l'accès aux dépôts internes) → 1.0.3 (désactivation dynamique des dépôts stock Rocky, sinon dnf tente systématiquement mirrors.rockylinux.org) → 1.0.4 (suppression pure des dépôts stock Rocky, plus seulement désactivés).

Consommé par /root/packer/scripts/internal-repo.sh : bootstrap minimal du dépôt indio-custom puis dnf install indio-baseline-config, qui dépose lui-même la configuration définitive — voir packer-template.md.

Schéma — quelle voie pour quelle VM

flowchart TD
    VM["Nouvelle VM (terraform apply, /root/vmware)"] --> Q{"Clonée depuis quel<br/>template ?"}
    Q -->|"tmpl-rocky96-hardened<br/>courant (RPM déjà intégré<br/>depuis le 2026-07-11)"| DONE["Déjà configurée au 1er boot<br/>dépôts + NTP + proxy + CA"]
    Q -->|"VM antérieure au rebuild<br/>du template (07-11)"| CATCHUP["Rattrapage manuel requis"]

    CATCHUP --> C1["rpm -q indio-baseline-config<br/>(IP ADMIN, pas IP service —<br/>hôtes DMZ multi-NIC)"]
    C1 --> C2{"Cas particulier ?"}
    C2 -->|"Serveur NTP lui-même<br/>(INDSERV005/006/007)"| C3["NE PAS installer le RPM complet<br/>(bouclerait sur lui-même) :<br/>fichiers proxy seuls +<br/>désactivation dynamique repos stock"]
    C2 -->|VM normale| C4["Patcher indio.repo existant<br/>(proxy=_none_) AVANT le RPM"]
    C4 --> C5["dnf install indio-baseline-config"]

    DONE --> ROT["set-passwords.yml si rotation décidée"]
    C5 --> ROT
    C3 --> ROT

    ROT --> AGENTS["agents.yml + syslog-forward.yml + audit-syslog.yml<br/>(toute la flotte, indépendant du RPM)"]

Provisioning Terraform

Non applicable. Ni /root/baseline (Ansible) ni /root/indio-baseline-config (RPM) ne provisionnent de VM — les deux agissent sur des VM déjà déployées par /root/vmware (rattrapage post-clonage) ou déjà présentes dans le template doré au clonage (voir packer-template.md).

Configuration Ansible (/root/baseline)

  • set-passwords.yml : playbook générique de rotation (hosts: fleet), module ansible.builtin.user sur root et indio-adm. Le hash du nouveau mot de passe n'est jamais stocké dans le dépôt : il est fourni en extra-var au lancement, par exemple ansible-playbook set-passwords.yml -e @<fichier externe non versionné> -e new_password_hash='<hash>'. La valeur elle-même est gérée séparément (Vault / non versionnée).
  • repo.yml : dépose l'entrée /etc/hosts pour repo.infra.infra, copie files/indio.repo (version à 3 sections : baseos/appstream/extras), rafraîchit le cache dnf du dépôt interne et journalise le statut de la résolution.
  • agents.yml + rôles associés : déploiement de l'agent Zabbix classique et de l'agent OCS Inventory NG sur toute la flotte, avec bascule vers zabbix-agent2 (plugin natif) sur les hôtes MySQL/PostgreSQL qui le nécessitent (les clés mysql.*/pgsql.* n'existent que dans l'agent2). Le rôle ocs_agent installe la CA Vault dans le trust store système pour la vérification TLS du serveur OCS, et compile l'agent depuis les sources (pas de RPM officiel EL9).
  • syslog-forward.yml (hosts: all) : drop-in rsyslog omfwd vers le serveur syslog-ng central (RFC5424/syslog-protocol, indispensable pour que syslog-ng détecte le bon format — voir syslogng.md), avec file d'attente disque et retry infini en cas de coupure réseau (action.resumeRetryCount="-1", queue.type="linkedList").
  • audit-syslog.yml (hosts: all) : relais des événements auditd en deux destinations parallèles — (1) audispd-plugins + activation du plugin syslog générique (rejoint le flux rsyslog standard ci-dessus) ; (2) rsyslog-kafka (module omkafka) + un module SELinux dédié (files/indio_rsyslog_kafka.pp, chargé via semodule -i) pour autoriser syslogd_t à joindre le port Kafka 9092 (bloqué nativement par la policy de référence, aucun label dédié Kafka) — sans ce module, le trafic est silencieusement rejeté (AVC denial). Le second volet alimente le topic Kafka logs-audit consommé ensuite par le pipeline Logstash → Elasticsearch. Piège important : le template durci active le mode audit immuable (auditctl -senabled 2) et auditd refuse un restart manuel — l'activation du plugin ne prend effet qu'après un reboot, jamais à chaud.
  • reboot-batch.yml : playbook générique de reboot par lot (serial: 1, --limit <groupe>) pour appliquer ce reboot en respectant les contraintes de chaque cluster ; vérifie après coup que le plugin audit est bien actif (grep "^active = yes" /etc/audit/plugins.d/syslog.conf).
  • Secret ansible-vault : group_vars/fleet.yml contient mysql_zbx_password chiffré ($ANSIBLE_VAULT), jamais consulté en clair pour cette documentation.

Packaging RPM (indio-baseline-config)

  • %install place chaque fichier statique à son emplacement final (yum.repos.d, profile.d, systemd/system.conf.d, chrony.d, pki/ca-trust, pki/rpm-gpg), avec %config(noreplace) sur les fichiers de configuration.
  • %post (idempotent, vérifie avant d'ajouter) :
  • Ajoute repo.infra.infra à /etc/hosts.
  • Ajoute une ligne proxy= globale dans /etc/dnf/dnf.conf (section [main] — dnf4 n'a pas de drop-in pour cette section).
  • Active confdir /etc/chrony.d dans chrony.conf et neutralise (commente) tout pool/ server public préexistant, pour que les 3 serveurs NTP internes soient prioritaires.
  • update-ca-trust extract (intègre les 2 CA + la clé EPEL).
  • systemctl daemon-reexec (propage DefaultEnvironment aux services déjà démarrés).
  • Sauvegarde puis supprime les fichiers .repo stock Rocky non préfixés indio- (rocky.repo et ses variantes -extras/-devel/-security/-addons) dans /var/lib/indio-baseline-config/stock-repos-removed/.
  • %postun (si désinstallation complète) : retire la ligne /etc/hosts, la ligne proxy= de dnf.conf, restaure pool/server dans chrony.conf, restaure les dépôts stock sauvegardés, régénère le trust store.
  • Build recommandé dans un conteneur rockylinux:9 (podman) plutôt qu'en natif, pour éviter tout écart de format RPM avec l'hôte de build.
  • Régénération des certificats CA (si besoin) : requête sur les endpoints PEM non authentifiés de la CA Vault, puis bump de version et rebuild — voir le README du dépôt.

Procédure de déploiement

VM nouvelles (clonées depuis le template Packer courant) : la configuration (dépôts, NTP, proxy, CA) est déjà présente au premier boot, le RPM étant intégré au template — aucune action /root/baseline n'est nécessaire pour ce volet. Seule une éventuelle rotation de mot de passe fleet-wide (set-passwords.yml) reste pertinente si une nouvelle rotation est décidée après le build.

VM existantes, antérieures à l'intégration du RPM dans le template : rattrapage manuel.

  1. Vérifier la présence du paquet (requête RPM en lecture seule) sur l'IP d'administration de la VM — pas l'IP de service, pour les hôtes multi-NIC en DMZ.
  2. Cas général : si un /etc/yum.repos.d/indio.repo préexistant a été déposé par repo.yml (Ansible), le patcher pour ajouter proxy=_none_ à chaque section avant d'installer le RPM — %config(noreplace) ne remplace pas un fichier déjà présent, et le proxy global casserait sinon l'accès aux dépôts internes.
  3. Cas particulier des serveurs NTP internes eux-mêmes : ne jamais leur installer le RPM complet (cela les ferait boucler sur eux-mêmes et perdre leur seule source de temps externe) — déployer uniquement les fichiers proxy (profile.d + drop-in systemd) et désactiver dynamiquement les dépôts stock, sans toucher à chrony.conf.
  4. Revérifier le service métier après coup (un dnf makecache réel), pas seulement le code de sortie de l'installation.

Les playbooks agents.yml, syslog-forward.yml et audit-syslog.yml s'exécutent sur toute la flotte indépendamment de la présence du RPM : ils couvrent un périmètre différent (supervision, inventaire, logs).

Contrôle de santé / Vérification

# Présence et version du RPM (sur l'IP admin, pas l'IP de service pour les hôtes DMZ)
ansible fleet -m command -a "rpm -q indio-baseline-config"

# Le dépôt interne répond réellement (pas seulement "RPM installé")
ansible fleet -m command -a "dnf -q --disablerepo=* --enablerepo=indio-* makecache"

# Synchronisation NTP effective sur les 3 serveurs internes
ansible fleet -m command -a "chronyc sources"

# Plugin audit actif après reboot (audit-syslog.yml + reboot-batch.yml)
ansible fleet -m command -a "grep '^active = yes' /etc/audit/plugins.d/syslog.conf"

Toujours revérifier le service métier réel (un dnf makecache qui aboutit, pas seulement le code de sortie de l'installation du RPM) — c'est cette vérification qui a permis de détecter le bug 1.0.0 (proxy global cassant l'accès aux dépôts internes) lors de sa découverte initiale.

Points d'attention

  • Le hardening SSH n'est pas appliqué par /root/baseline. Le drop-in /etc/ssh/sshd_config.d/00-hardening.conf (PermitRootLogin no, chiffrement restreint, etc.) est intégré au template doré au moment du build Packer (scripts/hardening.sh, voir packer-template.md) et hérité par toute VM clonée. /root/baseline se limite à la rotation du mot de passe des comptes existants (set-passwords.yml), pas à la configuration SSH elle-même.
  • Mots de passe : aucune valeur n'est stockée en clair dans /root/baseline ; le hash utilisé par set-passwords.yml est fourni en extra-var au lancement, depuis un fichier externe non versionné. Toute valeur observée ailleurs est gérée séparément (Vault / non versionnée) et ne doit jamais figurer dans la documentation.
  • Secret ansible-vault : group_vars/fleet.yml contient une variable chiffrée (mysql_zbx_password) ; le fichier de mot de passe du vault est référencé dans ansible.cfg mais n'est lui-même jamais versionné.
  • Redondance non résolue : files/indio.repo (Ansible, 3 sections) est une version obsolète par rapport à celle livrée par le RPM (6 sections, avec proxy=_none_) — dette technique identifiée dans le README du RPM, pas encore dédupliquée.
  • Le RPM active le proxy Squid par défaut sur toutes les VM (changement de posture assumé), y compris pour les services systemd (DefaultEnvironment) : ses propres dépôts internes doivent systématiquement être exemptés (proxy=_none_) sous peine de casser l'accès à Nexus.
  • Les scriptlets %post/%postun du RPM manipulent des fichiers potentiellement bind-montés (/etc/hosts) par grep -v ... > tmp && cat tmp > fichier plutôt que sed -i, qui échouerait sur un bind-mount.
  • Vérifier, selon la version du RPM en place, que le dépôt indio-epel pointe bien vers le miroir Nexus (repo.infra.infra) et non vers un upstream public direct — ce comportement a varié entre les premières versions du paquet.