FreeIPA & trust Active Directory

Rôle

FreeIPA fournit l'annuaire, l'authentification Kerberos et les services LDAP/DNS pour un royaume dédié ipa.infra.indio, distinct du domaine Active Directory existant infra.indio (voir Durcissement Active Directory). Une relation de confiance (trust) inter-royaumes relie les deux annuaires pour l'authentification croisée : les comptes et groupes AD (y compris les groupes intégrés) sont résolus côté FreeIPA et pilotent désormais l'accès SSH à 45 des 47 serveurs Linux de la flotte via une règle HBAC dédiée.

Le même dépôt Terraform/Ansible héberge également le bastion d'accès distant Guacamole (VM INDIDEN001, hors périmètre applicatif FreeIPA — voir Bastion Guacamole).

Demande initiale complètement traitée depuis le 2026-07-17

Trust établi et vérifié, flotte enrôlée, HBAC SSH actif. Cette page documente l'état réel après une histoire mouvementée (voir l'encadré « Chronologie » ci-dessous) — les versions précédentes de cette page décrivaient un trust encore à reconstruire manuellement ; ce n'est plus le cas.

Architecture

VM Rôle IP Notes
INDIDEN001 Bastion Guacamole 10.100.2.244 Installé manuellement, hors Ansible (cf. Bastion Guacamole) — inclus comme client IPA (ipa_clients_admin)
INDIDEN004 FreeIPA serveur primaire 10.100.2.245 ipa_primary, ipa-server-4.13.1-3.el9_8.2, DNS intégré (--setup-dns), adtrust actif (smb/winbind)
INDIDEN005 FreeIPA réplica 10.100.2.246 ipa_replica, DNS intégré, pas d'adtrust (smb/winbind absents — voir Points d'attention)
INDIDEN006 / INDIDEN007 VM génériques seg-identite 10.100.2.247 / 10.100.2.248 Provisionnées, aucun rôle serveur dédié, mais enrôlées comme clients IPA (ipa_clients_admin)
  • Segment réseau : seg-identite, 10.100.2.240/28, gateway 10.100.2.254, zone Admin (DFW default-ALLOW intra-zone), template tmpl-rocky96-hardened. Les deux contrôleurs de domaine AD (INDIDEN008 10.100.2.241, INDIDEN009 10.100.2.242, voir Durcissement Active Directory) sont sur ce même segment L2 que FreeIPA — pas de routage inter-segment pour la relation de confiance elle-même.
  • Domaine/royaume FreeIPA : ipa.infra.indio / IPA.INFRA.INDIO (sous-domaine du domaine AD, choisi précisément pour éviter toute collision de royaume Kerberos avec infra.indio / INFRA.INDIO).
  • Choix d'architecture assumé (« option B ») : les hôtes FreeIPA restent membres DNS du domaine AD infra.indio, mais le royaume Kerberos/IPA est distinct — d'où --no-host-dns à l'installation (le A record du serveur est géré côté AD, pas par FreeIPA).
  • Clients IPA enrôlés (45/47 serveurs Linux de la flotte, cf. inventory.ini réorganisé en 3 sous-groupes zonés ipa_clients_admin/_dmz/_soc) : toute la zone admin sauf 2 nœuds Kubernetes injoignables en réseau (INDSERV020/021, problème préexistant sans rapport), les 4 proxies DMZ (INDPRXY001/002, INDPRXD001/002, via leur NIC admin dédié — voir Convention de nommage), et 16 hôtes SOC.
  • INDIDEN002/003 (première installation FreeIPA) ont été détruites le 2026-07-07 et non reconstruites sous cette identité (voir « Chronologie »).

Chronologie (pour comprendre l'état actuel)

Trois cycles avant l'état opérationnel actuel

  1. 2026-07-05 : première installation, ipa-node01/ipa-node02 (INDIDEN002/003). Aucun trust AD établi (ipa trust-find vide).
  2. 2026-07-07 : les deux VM sont détruites à la demande explicite du user (terraform destroy -target), nettoyage complet effectué (NetBox, populate.py, inventaire Ansible). Le trust n'existant pas encore, rien à nettoyer côté AD. Les 4 proxies DMZ perdent leur enrôlement IPA (désenrôlés proprement le jour même).
  3. 2026-07-11 : 4 VM nues provisionnées en seg-identite (INDIDEN004-007), sans FreeIPA (commentaire variables.tf de l'époque : « VM nues (pas de FreeIPA) »).
  4. 2026-07-16 : le user installe FreeIPA manuellement sur INDIDEN004/005 (même pattern que Guacamole/MISP/TheHive/Cortex — installation hors Ansible). Bug critique introduit à cette occasion : le royaume Kerberos utilisé est INFRA.INDIO (identique au royaume AD) au lieu de IPA.INFRA.INDIO prévu — un trust est structurellement impossible entre deux royaumes Kerberos identiques (collision de krbtgt/basedn, pas juste déconseillé).
  5. 2026-07-17 : le mauvais royaume est détecté avant tout dégât (0 client enrôlé sur ce royaume). Désinstallation complète (réplica puis primaire, ipa-server-install --uninstall) suivie d'une réinstallation via ansible-playbook site.yml — cette fois avec le bon royaume et --setup-dns (DNS intégré à IPA, absent de l'installation manuelle précédente). Trust établi, flotte enrôlée, HBAC activé (voir sections dédiées ci-dessous).

Topologie du trust et flux DNS/HBAC

flowchart TB
    subgraph SEG["seg-identite — 10.100.2.240/28 (zone Admin, même segment L2)"]
        subgraph ADB["Active Directory — infra.indio / INFRA.INDIO"]
            DC1["INDIDEN008\n10.100.2.241\nDC primaire, tous rôles FSMO"]
            DC2["INDIDEN009\n10.100.2.242\nDC #2, Global Catalog"]
        end
        subgraph IPAB["FreeIPA — ipa.infra.indio / IPA.INFRA.INDIO"]
            P["INDIDEN004 primaire\n10.100.2.245\n+ smb/winbind (adtrust)"]
            R["INDIDEN005 réplica\n10.100.2.246\n(pas d'adtrust)"]
        end
    end
    DC1 -. "délégation DNS NS zone ipa\n+ A records indiden004/005" .-> P
    P == "ipa trust-add --type=ad infra.indio\n(résultat : Established and verified)" ==> DC1
    P -. "réplication LDAP 389/636" .-> R
    GRP["AD: GG_Infrastructure,\nAdmins du domaine (RID 512)"] -.->|"external groups IPA"| POSIX["ad-linux-admins (POSIX)"]
    POSIX --> HBAC["hbacrule allow-ssh-linux-admins\nhostgroup linux-servers (45 hôtes), service sshd"]
sequenceDiagram
    participant U as Utilisateur AD
    participant SSH as sshd (hôte cible)
    participant SSSD as SSSD (client IPA)
    participant IPA as FreeIPA (résolution trust + HBAC)
    U->>SSH: ssh utilisateur@infra.indio@hôte
    SSH->>SSSD: authentification PAM
    SSSD->>IPA: résolution identité via trust AD + évaluation HBAC
    alt Membre de ad-linux-admins (ex. pmartin, via GG_Infrastructure)
        IPA-->>SSSD: allow-ssh-linux-admins MATCH
        SSSD-->>SSH: authentication success + accepted
        SSH-->>U: session ouverte
    else Hors groupe HBAC (ex. hrichard, service Support)
        IPA-->>SSSD: aucune règle ne matche (allow_all désactivé)
        SSSD-->>SSH: Access denied by PAM account configuration
        SSH-->>U: Permission denied
    end

Règles DFW dédiées (zones deny-all)

La zone admin (seg-identite incluse) est en DFW default-ALLOW — aucune règle n'est nécessaire pour que le trafic intra-zone admin atteigne FreeIPA. En revanche, les zones DMZ et SOC sont en deny-all scopé (voir NSX) et nécessitent des règles explicites, ajoutées le 2026-07-17 :

Règle Fichier Source Destination Services
dmz-to-freeipa dfw_dmz.tf seg-proxy, seg-proxy-dns seg-identite svc-LDAP (Kerberos 88+464 TCP/UDP, LDAP 389/636 TCP), DNS (TCP/UDP), HTTP, HTTPS
soc-to-freeipa dfw_soc.tf seg-soc-admin, seg-soc seg-identite idem

Le service personnalisé svc-LDAP (services.tf) existe car les services NSX prédéfinis ne couvrent pas correctement Kerberos : il ajoute explicitement le port 464 (kpasswd, changement de mot de passe) en TCP et UDP en plus du 88 (authentification), aux côtés du LDAP/LDAPS standard.

Provisioning Terraform

Fichiers : main.tf, variables.tf, outputs.tf, versions.tf.

  • Providers hashicorp/vsphere (clonage VM) et e-breuninger/netbox (réservation/lecture d'IP par nom DNS via data.netbox_ip_addresses, filtrée sur dns_name en minuscule).
  • Une ressource vsphere_virtual_machine.vm en for_each sur var.nodes (liste name/cpu/ram, valeurs actuelles : INDIDEN001, 004, 005, 006, 007 — 2 vCPU/4096 Mo chacune), clonée depuis tmpl-rocky96-hardened, personnalisée (customize) avec hostname, domaine, IP/masque/gateway/DNS.
  • lifecycle.ignore_changes sur annotation, template_uuid, customize et disk[0].io_share_count pour éviter un drift Terraform sur des VM déjà personnalisées manuellement après clonage (essentiel ici : FreeIPA et Guacamole modifient abondamment ces VM après le clone).
  • outputs.tf expose la map nom → IP des VM créées.
  • Le commentaire du fichier variables.tf qualifiait encore récemment INDIDEN004-007 de « VM nues (pas de FreeIPA) » — obsolète depuis l'installation du 2026-07-16 pour 004/005 ; non corrigé dans le code à ce jour (voir Points d'attention).

Configuration Ansible

  • inventory.ini : groupes ipa_primary, ipa_replica, ipa:children, guacamole, et ipa_clients réorganisé en 3 sous-groupes zonés (ipa_clients_admin/_dmz/_soc) pour permettre un rollout par lots (--limit) avec vérification entre chaque lot.
  • site.yml : deux plays séquentiels — serveur primaire (rôles freeipa_common + freeipa_primary) puis réplica (freeipa_common + freeipa_replica). Le bastion Guacamole est explicitement exclu de ce playbook (installation manuelle) : c'est ce même site.yml, jusque-là jamais utilisé pour une installation réelle, qui a servi à la réinstallation propre du 2026-07-17.
  • enroll-clients.yml : playbook séparé qui applique le rôle ipa_client au groupe ipa_clients.
  • group_vars/all.yml : domaines/royaumes IPA et AD, forwarders DNS (10.100.2.241/242), liste des ports firewalld à ouvrir (dont le bloc adtrust : SMB/RPC/Global Catalog — 135, 138, 139, 445/tcp+udp, 3268, 3269, 49152-65535/tcp). Les mots de passe ipa_dm_password et ipa_admin_password sont stockés chiffrés (!vault / $ANSIBLE_VAULT) — valeurs gérées séparément, non reproduites ici.
  • ansible.cfg référence un fichier de mot de passe vault dédié (/root/.identity_vault_pass).

Rôles

Rôle Contenu
freeipa_common Hostname FQDN, résolution /etc/hosts des deux serveurs IPA, installation des paquets ipa-server, ipa-server-dns, ipa-server-trust-ad, ouverture firewalld des ports IPA + adtrust
freeipa_primary ipa-server-install --unattended avec --setup-dns, --setup-adtrust, --netbios-name=IPA, --no-host-dns, --no-reverse, --no-dnssec-validation, --no-ntp (exécution asynchrone, poll jusqu'à 30 min)
freeipa_replica Résolveur pointé vers le primaire, enrôlement explicite comme client IPA (domaine/royaume/serveur imposés, pour éviter la découverte SRV automatique qui trouverait l'AD), puis ipa-replica-install --setup-ca --setup-dns — nécessite --principal admin --admin-password pour la promotion
ipa_client Résolution locale, installation ipa-client, désenrôlement préalable d'un éventuel ancien client obsolète (garde-fou failed_when: false), puis ipa-client-install --mkhomedir --force-join

Ce que ces rôles ne font pas, et qui reste manuel (voir section suivante) : établissement du trust lui-même, délégation DNS côté AD, et toute la configuration HBAC (groupes, règles, désactivation de allow_all).

Procédure manuelle

Ces étapes ne sont couvertes par aucun playbook Ansible ni ressource Terraform — reconstituées ici comme procédure numérotée reproductible à partir de l'état vérifié le 2026-07-17.

1. Délégation DNS côté AD (préalable indispensable au trust)

ipa.infra.indio est un sous-domaine de infra.indio, zone dont l'AD est déjà autoritaire — un simple conditional forwarder est rejeté par Windows DNS dans ce cas précis (Add-DnsServerConditionalForwarderZone échoue avec WIN32 9604 / DNS_ERROR_ZONE_CONFIGURATION_ERROR : un DC ne peut pas créer de forwarder conditionnel pour un sous-domaine d'une zone dont il est déjà autoritaire). Il faut une vraie délégation de zone :

# Sur un DC AD (INDIDEN008), PowerShell DnsServer
Add-DnsServerZoneDelegation -Name infra.indio -ChildZoneName ipa `
    -NameServer indiden004.infra.indio -IPAddress 10.100.2.245
Add-DnsServerResourceRecord -ZoneName infra.indio -NS -Name ipa `
    -NameServer indiden005.infra.indio
# A records des serveurs IPA eux-mêmes, absents malgré la mention
# "hors Terraform, tâche user" dès la conception initiale du projet
Add-DnsServerResourceRecordA -ZoneName infra.indio -Name indiden004 -IPv4Address 10.100.2.245
Add-DnsServerResourceRecordA -ZoneName infra.indio -Name indiden005 -IPv4Address 10.100.2.246

Vérification bidirectionnelle : _kerberos._tcp.ipa.infra.indio doit se résoudre depuis un DC AD, _ldap._tcp.infra.indio doit se résoudre depuis un serveur IPA (forward configuré par ipa_forwarders dans group_vars).

2. Établissement du trust

# Sur INDIDEN004 (primaire IPA), après kinit admin@IPA.INFRA.INDIO
ipa trust-add --type=ad infra.indio --admin Administrateur --password
# (nom localisé FR — ce domaine AD est installé en français, cf.
# Durcissement Active Directory) -> "Trust status: Established and verified"

Vérifié fonctionnel immédiatement, sans retry, y compris pendant la promotion simultanée du réplica (pas de race constatée entre écriture LDAP sur le primaire et réplication en cours).

3. Enrôlement de la flotte

ansible-playbook enroll-clients.yml --limit ipa_clients_admin
ansible-playbook enroll-clients.yml --limit ipa_clients_dmz
ansible-playbook enroll-clients.yml --limit ipa_clients_soc

Par lots zonés avec vérification entre chaque (et non un seul run global) — c'est ce séquencement qui a permis d'isoler le trou DFW SOC décrit en Points d'attention plutôt que d'échouer en bloc sans diagnostic clair.

4. Configuration HBAC (restriction de l'accès SSH aux groupes AD attendus)

# Groupe hôte couvrant toute la flotte Linux
ipa hostgroup-add linux-servers --desc="Flotte Linux Indio (SSH via trust AD)"
ipa hostgroup-add-member linux-servers --hosts=indserv002.infra.indio,...

# Groupes externes = pointeurs vers des groupes AD (côté trust)
ipa group-add ad-gg-infrastructure-external --external
ipa group-add ad-domain-admins-external --external
ipa group-add ad-linux-admins --desc="Comptes AD autorisés en SSH sur la flotte"

# ATTENTION : chaque commande *-add-member ci-dessous est interactive par
# défaut (voir piège CLI en Points d'attention) — à exécuter une par une,
# jamais en pipe multi-lignes vers un même stdin.
ipa group-add-member ad-gg-infrastructure-external --external "INFRA.INDIO\GG_Infrastructure"
ipa group-add-member ad-domain-admins-external --external "INFRA.INDIO\Admins du domaine"
ipa group-add-member ad-linux-admins --groups=ad-gg-infrastructure-external
ipa group-add-member ad-linux-admins --groups=ad-domain-admins-external

# Règle HBAC dédiée, puis désactivation de la règle par défaut
ipa hbacrule-add allow-ssh-linux-admins
ipa hbacrule-add-user allow-ssh-linux-admins --groups=ad-linux-admins
ipa hbacrule-add-host allow-ssh-linux-admins --hostgroups=linux-servers
ipa hbacrule-add-service allow-ssh-linux-admins --hbacsvcs=sshd
ipa hbacrule-disable allow_all

T0-Admins (groupe AD du volet durcissement, vide au moment du choix) a été écarté du mapping ; GG_Infrastructure et Admins du domaine retenus car seuls groupes avec des membres réels à ce moment (pmartin, sbernard, Administrateur).

Procédure de déploiement

  1. terraform apply (racine du repo) — provisionne les VM sur seg-identite.
  2. ansible-playbook site.yml — installe le serveur FreeIPA primaire (INDIDEN004, --setup-dns inclus), puis le réplica (INDIDEN005), dans cet ordre (dépendance).
  3. Délégation DNS côté AD (Procédure manuelle, étape 1) — préalable au trust.
  4. ipa trust-add --type=ad infra.indio (Procédure manuelle, étape 2).
  5. ansible-playbook enroll-clients.yml --limit <groupe> par lots zonés (Procédure manuelle, étape 3) — vérifier au préalable que les règles DFW dmz-to-freeipa/soc-to-freeipa sont appliquées côté NSX si les cibles sortent de la zone admin.
  6. Configuration HBAC (Procédure manuelle, étape 4).
  7. Bastion Guacamole (INDIDEN001) : installation manuelle séparée, voir Bastion Guacamole.

Contrôle de santé / Vérification

# État du trust
ipa trust-show infra.indio
# -> Trust status: Established and verified

# Résolution d'identité cross-royaume (utilisateur + groupes AD, y compris
# noms localisés FR)
id pmartin@infra.indio

# État de la règle HBAC et confirmation qu'allow_all est bien désactivée
ipa hbacrule-show allow-ssh-linux-admins
ipa hbacrule-show allow_all   # Enabled: FALSE attendu

# Vérification décisive (pas seulement le comportement du client SSH,
# ambigu) : lire les logs PAM/SSSD sur l'hôte cible
journalctl -u sshd -u sssd --since "-10min"
# Cas autorisé  : "Accepted password" + session ouverte
# Cas refusé    : "Access denied ... by PAM account configuration"
#                 (distinct sans ambiguïté d'un échec d'authentification)

Le compte local indio-adm (pam_unix) n'est jamais concerné par ces vérifications — HBAC ne s'applique qu'aux identités IPA/AD via SSSD.

Pour la santé du domaine AD sous-jacent (réplication, services core, PKI), voir le contrôle de santé documenté dans Durcissement Active Directory.

Points d'attention

Royaume Kerberos erroné lors de l'installation manuelle du 07-16

L'installation manuelle du 2026-07-16 avait utilisé le royaume INFRA.INDIO (identique à l'AD) au lieu de IPA.INFRA.INDIO prévu — un trust est structurellement impossible entre deux royaumes Kerberos identiques (collision krbtgt/basedn, pas un simple défaut de configuration). Détecté avant tout dégât (0 client enrôlé sur ce royaume), corrigé par un cycle complet désinstall/réinstall via le playbook site.yml — la leçon généralisable : toujours vérifier /etc/ipa/default.conf et /etc/krb5.conf après toute installation manuelle d'un service Kerberos, avant de supposer que la configuration correspond à ce que documente le dépôt IaC.

  • Piège CLI FreeIPA généralisable : toute commande ipa <resource>-add-member/-add-user/-add-host/-add-service déclenche 4 prompts interactifs séquentiels dès qu'aucun de --users/--groups/--services/--idoverrideusers n'est fourni explicitement — même avec --external=..., qui ne supprime pas ces prompts contrairement à l'intuition. Exécutée via un script multi-lignes piped en une seule fois vers un shell distant (cat script.sh | ssh host "bash -s"), ces prompts consomment silencieusement les lignes suivantes du script comme fausses réponses interactives — plusieurs commandes ultérieures ne sont alors jamais exécutées, sans erreur visible autre qu'un « Failed members » contenant des bouts de texte du script lui-même. Toujours exécuter une commande ipa *-add-member par appel SSH séparé.
  • Piège découvert en testant le HBAC : la politique de mot de passe AD durcie exige 14 caractères minimum (voir Durcissement Active Directory) — un mot de passe de 13 caractères échoue silencieusement côté Set-ADAccountPassword ($ErrorActionPreference par défaut Continue, aucune exception levée), puis fait échouer le SSH suivant avec un Preauthentication failed trompeur qui ressemble à un refus HBAC. Toujours vérifier la longueur du mot de passe et entourer les cmdlets AD sensibles de try/catch -ErrorAction Stop.
  • Trou DFW rencontré en conditions réelles lors de l'enrôlement SOC : les règles dmz-to-freeipa/soc-to-freeipa posées initialement omettaient le port HTTP (80/tcp), qu'ipa-client-install exige en plus de HTTPS. Les proxies DMZ avaient réussi malgré tout via un chemin détourné (règle HTTP générale préexistante non scopée), ce qui a masqué le trou jusqu'à l'échec explicite des 10 premiers hôtes SOC (sans route de secours équivalente). Corrigé en ajoutant /infra/services/HTTP aux deux règles.
  • Pas de haute disponibilité du trust lui-même : ipa-adtrust-install n'a pas été exécuté sur le réplica (INDIDEN005, smb/winbind absents de son ipactl status) — le trust fonctionne via le primaire seul ; sa panne interromprait la résolution d'identité AD côté FreeIPA (le LDAP/ Kerberos IPA de base resterait néanmoins disponible via le réplica).
  • HBAC limité à SSH (sshd) comme demandé explicitement — aucune règle sudo ou autre service créée à ce jour.
  • Le commentaire variables.tf (« VM nues, pas de FreeIPA ») pour INDIDEN004-007 est resté obsolète dans le code après l'installation du 07-16 — à corriger si ce repo est repris, pour éviter qu'un futur lecteur ne se fie au commentaire plutôt qu'à l'état live.
  • INDSERV020/INDSERV021 (nœuds Kubernetes, voir Cluster k8s) restent injoignables réseau et non enrôlés — problème préexistant sans rapport avec ce projet.
  • Les mots de passe Directory Manager et admin IPA sont chiffrés via ansible-vault dans group_vars/all.yml — jamais en clair dans le dépôt.