NSX¶
Rôle¶
Micro-segmentation réseau de toute l'infrastructure Indio via VMware NSX-T 4.2 (NSX Manager 10.15.100.15). Le dépôt /root/nsx pilote, au travers du provider Terraform vmware/nsxt, l'ensemble des objets créés par l'administrateur de ce Manager : topologie de routage (T0/T1), segments réseau, groupes, services personnalisés et règles de pare-feu distribué (DFW).
Dépôt issu d'une rétro-ingénierie
Le code a été généré par export de la configuration existante (API Policy) puis reconstruit à l'identique — voir _export_raw/generate_tf.py et _export_raw/*.json. Au moment de sa génération, un terraform plan avec les blocs d'import donnait 43 to import, 0 to add, 0 to change, 0 to destroy : aucune dérive entre le code et le NSX Manager réel. Le fichier imports.tf qui portait ces 43 blocs a depuis été supprimé du dépôt (comportement attendu, documenté dans le README : il ne sert qu'au premier apply).
Seuls les objets _create_user = admin sont gérés : les ~415 services et 64 profils système livrés par défaut avec NSX sont volontairement exclus du code et seulement référencés par leur path quand une règle les utilise.
Architecture¶
Routage : un T0 en BGP, trois T1 par zone¶
| Gateway | Rôle | Détail |
|---|---|---|
T0-Edge |
Tier-0, ACTIVE_ACTIVE | AS 65001, ECMP, 3 uplinks (10.15.91.1/28, .2/28, .3/28), voisin BGP FortiGate AS 65010 (10.15.91.14) |
T1-Admin |
Zone admin | Supervision, identité, backup, stockage — DHCP intégré |
T1-DMZ |
Zone DMZ | Proxys frontaux (Squid + DNS) |
T1-SOC |
Zone SOC | Outils de sécurité (SIEM) — DHCP intégré |
Chaque T1 est raccordé au T0 (route_advertisement_types = ["TIER1_CONNECTED"], failover_mode = NON_PREEMPTIVE).
Segments (constat direct du code, 11 overlay + 2 VLAN)¶
| Segment | Zone | CIDR | DHCP | Description |
|---|---|---|---|---|
seg-sauvegarde |
Admin | 10.100.3.30/28 |
non | Agents de backup (Veeam, NetBackup…) |
seg-supervision |
Admin | 10.100.2.238/28 |
non | Monitoring, syslog, SNMP, Zabbix/Prometheus |
seg-identite |
Admin | 10.100.2.254/28 |
non | Active Directory, LDAP, Kerberos, PKI |
seg-admin |
Admin | 10.100.2.126/25 |
oui (.2-.121) |
Postes administrateurs, jump hosts |
seg-data |
Admin | 10.100.2.222/27 |
non | Données et bases d'administration |
seg-service |
Admin | 10.100.2.190/26 |
non | Services partagés d'administration |
seg-storage |
Admin | 10.100.3.14/28 |
oui (.2-.13) |
Interfaces mgmt NAS/SAN/vSAN — profil MAC learning dédié |
seg-soc-admin |
SOC | 10.100.20.1/26 |
oui (.10-.60) |
SOC — administration des outils |
seg-soc |
SOC | 10.100.20.129/25 |
non | SOC — sous-réseau général |
seg-proxy |
DMZ | 10.100.10.1/29 |
non | Proxy applicatif Squid (voir Proxy Squid & DNS) |
seg-proxy-dns |
DMZ | 10.100.10.9/29 |
non | Proxy DNS unbound (voir Proxy Squid & DNS) |
seg-t0-uplink (VLAN) |
— | VLAN 4091 | — | Uplink T0 côté FortiGate |
seg-edge-uplinks (VLAN) |
— | VLAN 0-4094 | — | Trunk uplinks Edge |
seg-storage porte un nsxt_policy_mac_discovery_profile dédié (mac_learning_enabled = true, mac_change_enabled = true, unknown_unicast_flooding_enabled = true) : les nœuds du vSAN File Service émettent avec des MAC non enregistrées auprès du segment, et le profil par défaut les rend injoignables (« no route to host ») malgré une gateway qui répond.
Vue d'ensemble de la topologie (constat direct de tier0.tf/tier1.tf/segments.tf) :
flowchart TD
FORTI["FortiGate AS 65010<br/>10.15.91.14 (SNAT overlay)"]
T0["T0-Edge — ACTIVE_ACTIVE<br/>AS 65001 · BGP · ECMP<br/>3 uplinks 10.15.91.1-3/28"]
FORTI <-->|BGP| T0
T0 --> T1A["T1-Admin<br/>DHCP intégré"]
T0 --> T1D["T1-DMZ"]
T0 --> T1S["T1-SOC<br/>DHCP intégré"]
subgraph ZA[" Zone admin — DFW default ALLOW "]
T1A --> SAUV["seg-sauvegarde<br/>10.100.3.30/28"]
T1A --> SUPV["seg-supervision<br/>10.100.2.238/28"]
T1A --> IDEN["seg-identite<br/>10.100.2.254/28"]
T1A --> ADMN["seg-admin<br/>10.100.2.126/25 (DHCP)"]
T1A --> DATA["seg-data<br/>10.100.2.222/27"]
T1A --> SERV["seg-service<br/>10.100.2.190/26"]
T1A --> STOR["seg-storage<br/>10.100.3.14/28 (DHCP, MAC learning)"]
end
subgraph ZD[" Zone DMZ — deny-all scopé "]
T1D --> PRXY["seg-proxy<br/>10.100.10.1/29"]
T1D --> PRXD["seg-proxy-dns<br/>10.100.10.9/29"]
end
subgraph ZS[" Zone SOC — deny-all scopé "]
T1S --> SOCA["seg-soc-admin<br/>10.100.20.1/26 (DHCP)"]
T1S --> SOCG["seg-soc<br/>10.100.20.129/25"]
end
Groupes, services et objets de référence¶
- Groupes (
nsxt_policy_group) : trois familles de critères cohabitent — IP fixe/CIDR (ipaddress_expression, ex.grp-vcenter,grp-nsx-manager), appartenance à un segment (path_expression, ex.grp-seg-storage), et tag VM (condition, ex.grp-zone-admin). Ils sont répartis entregroups.tf(groupes génériques) et les fichiersdfw_*.tf(groupes posés à côté de la policy qui les consomme :dfw_dmz.tf,dfw_soc.tf,dfw_service.tf,dfw_extra.tf). - Services personnalisés (
nsxt_policy_service) : 8 dansservices.tf(svc-FleetServer,svc-Backup,svc-NFS-Full,svc-DNS-SSTP-ICMP,svc-LDAP,svc-Monitoring,svc-NSX-Intelligence,svc-StorageMgmt) + 2 posés directement dansdfw_dmz.tf(svc-Squid,svc-VRRP).svc-NFS-Fullexiste car les services NFS prédéfinis de NSX ne couvrent que 2049 (TCP/UDP) et 111/TCP, pas 111/UDP ni les ports mountd/lock/status utilisés par le vSAN File Service. ip_pools.tf:TEP-Pool-Overlay(10.10.30.0/24, plage.20-.120) pour l'adressage TEP overlay.dhcp.tf: deux serveurs DHCP intégrés aux gateways (dhcp-T1-Admin,dhcp-T1-SOC), adresses link-local169.254.x.1/24.data.tf: objets préexistants non gérés par ce module —TZ-Overlay,TZ-VLAN,EC-Edge-01.
Distributed IDS/IPS (Threat Prevention)¶
Module de détection/prévention d'intrusion distribué (licence NSX Data
Center Distributed Threat Prevention, active). Le host prep du cluster
(activation d'IDS/IPS sur les hyperviseurs) avait été fait manuellement en
amont (_create_user = admin) — absorbé dans idps.tf par import Terraform,
même logique que le reste du dépôt.
nsxt_policy_idps_cluster_config: active IDS/IPS sur le compute cluster vSphere (data.nsxt_compute_collection, nom paramétrable viavar.vsphere_cluster_name, défaut"Cluster").nsxt_policy_idps_settings(singleton global) : pas de mise à jour automatique des signatures (auto_update_signatures = false— ce Manager n'a pas d'accès confirmé vers le service de signatures VMware, la versionDEFAULTembarquée reste utilisée telle quelle) ; export des événements IDS/IPS vers syslog activé (enable_syslog = true).- Profils (
nsxt_policy_intrusion_service_profile) :IDS-Critical-High(sévéritésCRITICAL/HIGH, zones DMZ et Admin) etIDS-Full-Coverage(toutes sévérités, zone SOC — visibilité maximale sur les outils de sécurité eux-mêmes). - Policy (
nsxt_policy_intrusion_service_policy.idps_global, 3rule) : une règle par zone (DMZ, SOC, Admin), scope par les groupes de segment déjà existants (grp-seg-*),action = "DETECT"partout — mode détection pure pour ce premier déploiement, pas de blocage actif (IPS) tant qu'une période d'observation n'a pas écarté les faux positifs sur le trafic réel. - Export syslog réel : le NSX Manager (hors overlay,
10.15.100.15) n'est pas un nœud de la flotte/root/baseline— l'exportateur syslog (exporter_name=syslog-ng-soc, UDP/514 vers10.100.20.130) a été configuré à part via l'API node (/api/v1/node/services/syslog/exporters), hors Terraform (pas de ressource dédiée dans le providervmware/nsxt). Nécessite la règle DFWmgmt-to-soc-syslog(dfw_soc.tf, sourcegrp-mgmt-nat= IP SNAT FortiGate) — vérifié bout en bout (logs NSX visibles dans/var/log/syslogng/NSX/côté syslog-ng).
flowchart LR
ESXI["Hyperviseurs ESXi du cluster<br/>host prep IDS/IPS actif<br/>(nsxt_policy_idps_cluster_config)"]
ESXI --> DMZT["Trafic zone DMZ<br/>seg-proxy / seg-proxy-dns"]
ESXI --> ADMT["Trafic zone Admin<br/>7 segments seg-*"]
ESXI --> SOCT["Trafic zone SOC<br/>seg-soc / seg-soc-admin"]
DMZT --> RDMZ["rule idps-dmz<br/>profil IDS-Critical-High"]
ADMT --> RADM["rule idps-admin<br/>profil IDS-Critical-High"]
SOCT --> RSOC["rule idps-soc<br/>profil IDS-Full-Coverage"]
RDMZ --> DETECT["action = DETECT (pas de blocage)<br/>logged = true"]
RADM --> DETECT
RSOC --> DETECT
DETECT --> NSXMGR["NSX Manager 10.15.100.15<br/>enable_syslog = true"]
NSXMGR -->|"exportateur API node<br/>syslog-ng-soc, UDP/514<br/>(hors Terraform)"| GATE{"DFW SOC deny-all scopé<br/>rule mgmt-to-soc-syslog<br/>source grp-mgmt-nat"}
GATE -->|autorisé| SYSLOG["syslog-ng<br/>INDSOC001 · 10.100.20.130"]
SYSLOG --> LOGS["/var/log/syslogng/NSX/"]
- Non fait, évalué : Identity Firewall (règles DFW basées sur
l'identité AD, ressource
nsxt_policy_ldap_identity_sourcedisponible dans le provider, prérequis déjà en place — comptesvc-nsx-bind, LDAPS déjà actif sur les 2 DC) — réalisable mais pas implémenté, à faire sur demande. Malware Prevention et NSX Intelligence/NDR nécessitent le déploiement de NSX Application Platform (cluster Kubernetes dédié, composant séparé non provisionné) — hors de portée sans un provisioning massif supplémentaire ; la licence correspondante (NSX Firewall with Advanced Threat Prevention) est présente mais à quantité 0, donc non réellement exploitable en l'état.
Provisioning Terraform¶
- Provider
vmware/nsxt >= 3.7.0, Terraform>= 1.5.0. - Variables (
variables.tf) :nsx_manager,nsx_username(défautadmin),nsx_password(sensitive),nsx_insecure(défauttrue— certificat auto-signé). - Fichiers de ressources et contenu réel constaté (le README du dépôt liste un découpage légèrement plus ancien — voir encadré ci-dessous) :
| Fichier | Contenu |
|---|---|
tier0.tf |
T0-Edge + 3 interfaces uplink + voisin BGP FortiGate |
tier1.tf |
T1-Admin, T1-DMZ, T1-SOC |
segments.tf |
11 segments overlay + 2 VLAN + profil MAC learning vSAN |
services.tf |
8 services personnalisés |
groups.tf |
Groupes génériques (IP, segment, tag) |
security_policies.tf |
DFW-InterZone-Admin, DFW-Zone-Admin, DFW-Complements |
dfw_dmz.tf |
Groupes + services + policy DFW-Zone-DMZ |
dfw_service.tf |
Policy DFW-Service-Apps + groupe GLPI |
dfw_soc.tf |
Policy DFW-Zone-SOC + groupes SOC |
dfw_extra.tf |
Groupe grp-ad-dns |
dhcp.tf |
Serveurs DHCP T1-Admin / T1-SOC |
ip_pools.tf |
Pool TEP overlay |
data.tf |
Data sources infra préexistante |
idps.tf |
Distributed IDS/IPS : host prep cluster, settings globaux, profils et policy |
README partiellement daté
Le README du dépôt décrit un état antérieur (2 security policies, 17 groupes, 7 segments overlay, 6 services). Le code a évolué depuis (ajout des zones DMZ/SOC détaillées, GLPI, Aria Operations for Logs, Zabbix, OCS…) : cette page reflète le contenu réel des fichiers .tf au 2026-07-17, pas le README.
Configuration Ansible¶
Aucune — ce dépôt ne contient pas de dossier ansible/. NSX est piloté uniquement via l'API Policy par Terraform ; il n'y a pas de configuration au niveau OS/applicatif à gérer ici.
Procédure manuelle¶
Point central de ce dépôt, déjà signalé dans l'encadré en tête de page mais jamais détaillé jusqu'ici : la quasi-totalité de la configuration NSX décrite par ce Terraform (T0/T1, segments, groupes, services, règles DFW) a été construite à la main dans le NSX Manager — UI et/ou appels API directs en tant qu'admin — avant même que ce dépôt existe. Ce n'est pas un provisioning ab initio : c'est de la rétro-ingénierie d'un existant déjà en place. Deux choses distinctes sont documentées ci-dessous : (1) la méthode de rétro-ingénierie elle-même — réutilisable si un futur objet est un jour créé à la main dans NSX Manager et qu'on veut le faire entrer sous IaC — et (2) un petit nombre de réglages qui restent, aujourd'hui encore, gérés en dehors de tout Terraform.
1. Comment ce dépôt a été construit à partir de l'existant (rétro-ingénierie)¶
Outillage conservé dans _export_raw/ (ignoré par git, présent uniquement en
local) :
- Export brut : un fichier JSON par type d'objet, obtenu par des
GETen lecture seule contre l'API Policy du NSX Manager (10.15.100.15) — les noms de fichiers correspondent directement aux endpoints standards de cette API (tier0s.json↔GET /policy/api/v1/infra/tier-0s,tier1s.json↔.../tier-1s,segments.json↔.../segments,groups.json↔.../domains/default/groups,services.json↔.../services,security_pol.json↔.../domains/default/security-policies, et de même pourt0_interfaces,t0_bgp/t0_bgp_neighbors,dhcp_server_cfg,ip_pools/ip_pool_subnets,context_profiles,edge_clusters,gateway_pol). La séquence exacte des appels (script ad hoc ou requêtes manuelles au fil de l'eau) n'est pas tracée dans l'historique du projet — seul le résultat (les JSON) a été conservé. - Génération du Terraform :
_export_raw/generate_tf.py(404 lignes, toujours présent sur disque) lit ces JSON, filtre aux seuls objets créés par un utilisateur (not _system_owned and _create_user != "system"— exclut les ~415 services et 64 profils système livrés par défaut avec NSX), puis émet les fichiers.tfun par type d'objet. Point technique clé : le script maintient une table de résolutionpath Policy → référence HCL(dictREF) au fur et à mesure qu'il traite les objets dans un ordre qui respecte les dépendances (T0 avant T1, T1 avant segments, objets avant groupes qui les référencent, etc.) — une référence croisée entre deux objets user devient une expression Terraform (nsxt_policy_xxx.yyy.path) plutôt qu'une chaîne de caractères, ce qui rend le code résultant lisible et refactorable au lieu d'un simple recopiage de paths bruts. - Réconciliation du state : les objets déjà réels côté NSX ne peuvent
pas être « créés » par un premier
terraform applysans provoquer un conflit — un fichierimports.tftemporaire (43 blocsimport {}, un par ressource) a été généré en parallèle du reste, pointant chaque ressource Terraform vers l'ID réel de l'objet NSX correspondant (format d'ID spécifique par type, ex.<gateway>/<locale-service>/<id>pour une interface T0 — détail dans le README du dépôt).terraform planavec ces blocs a montré43 to import, 0 to add, 0 to change, 0 to destroy: preuve que le code généré correspondait exactement à la config réelle, aucune divergence introduite par la traduction JSON→HCL. terraform applya alors peuplé le state à partir de ces imports (aucune écriture réelle côté NSX, diff nul).imports.tfa ensuite été supprimé — il ne sert qu'à ce premier rattachement, le garder provoque sinon des tentatives d'import répétées à chaqueplansuivant.
Cette méthode (export ciblé par type d'objet → script de génération avec
table de résolution de références → réconciliation par blocs import →
suppression des imports) est directement réutilisable pour tout futur objet
NSX créé hors Terraform (à la main ou par un autre outil) qu'on voudrait
faire entrer sous IaC sans le recréer.
2. Ce qui reste configuré en dehors de Terraform aujourd'hui¶
- Export syslog du NSX Manager : le NSX Manager lui-même (nœud
d'administration, pas un objet du modèle Policy) exporte ses logs vers
syslog-ng(exporter_name=syslog-ng-soc, protocole UDP, port 514, cible10.100.20.130). Configuré via l'API node, pas l'API Policy — aucune ressource du providervmware/nsxtne couvre cet objet :
curl -sk -u admin:'<mot de passe NSX>' -X POST \
https://10.15.100.15/api/v1/node/services/syslog/exporters \
-H 'Content-Type: application/json' \
-d '{
"exporter_name": "syslog-ng-soc",
"protocol": "UDP",
"server": "10.100.20.130",
"port": "514",
"level": "INFO"
}'
Nécessite la règle DFW mgmt-to-soc-syslog (dfw_soc.tf, source
grp-mgmt-nat = IP SNAT FortiGate) déjà en place côté Terraform — seul
l'export lui-même échappe à l'IaC.
- Host prep Distributed IDS/IPS : l'activation d'IDS/IPS sur le compute
cluster (licence Distributed Threat Prevention) a été faite avant
l'écriture d'idps.tf, en tant qu'admin (_create_user = admin, même
logique que le reste du dépôt), puis absorbée par le même mécanisme
d'import que les autres objets. La méthode standard NSX pour ce type
d'activation est NSX Manager → Security → Distributed IDS/IPS → Cluster
Settings → Enable sur le cluster cible (ou l'appel API Policy
équivalent) — mais la séquence exacte réellement suivie sur ce Manager
n'est pas tracée dans l'historique du projet, seul le résultat final
(nsxt_policy_idps_cluster_config importé, cf. Architecture ci-dessus)
est connu avec certitude.
- Plus généralement : toute la configuration T0/T1/segments/groupes/
services/DFW décrite dans ce dépôt a, par construction, été créée à la
main avant d'être rétro-documentée (voir section 1) — il n'existe pas de
procédure « from scratch » validée pour reconstruire ce NSX Manager
entièrement par terraform apply sur un environnement vierge (aucun T0,
aucune zone de transport, aucun Edge n'est créé par ce code, voir
data.tf — ce sont des prérequis d'infrastructure NSX considérés comme
déjà en place).
Procédure de déploiement¶
cp terraform.tfvars.example terraform.tfvars # renseigner le mot de passe NSX
terraform init
terraform plan # doit montrer 0 changement si rien n'a dérivé côté NSX Manager
terraform apply
Le terraform.tfstate est local (ignoré par git avec terraform.tfvars) ; pour un usage en équipe, le README suggère un backend distant (voir le bucket S3 disponible côté AWS, bien que ce dépôt ne l'utilise pas actuellement).
Points d'attention¶
- Une règle
direction = OUT(ouIN) seule ne couvre que les connexions initiées dans ce sens. Le caractèrestateful = truedes security policies fait que les paquets retour d'une connexion déjà établie sont automatiquement acceptés — mais dès qu'un flux logiquement distinct doit être initié dans le sens opposé (ex. un serveur qui vient interroger activement un agent, en plus de l'agent qui pousse vers le serveur), c'est une connexion différente qui exige sa propre règle. Dans une zone à deny-all scopé (DMZ, SOC), l'absence de cette règle complémentaire fait tomber ce second flux dans le deny-all — silencieusement. Exemple réel et documenté dans le code (dfw_dmz.tf,dfw_soc.tf) : la supervision Zabbix a nécessité deux règles séparées de part et d'autre —dmz-to-zabbix/zabbix-to-dmzetsoc-to-zabbix/zabbix-to-soc— le polling passif (10050, le serveur interroge l'agent) restait bloqué tant que le pendant symétrique de la règle active (10051) n'existait pas. -
Ordre d'évaluation (« cascade ») : NSX évalue d'abord par catégorie fixe (
Ethernet>Emergency>Infrastructure>Environment>Application), puis parsequence_numbercroissant au sein d'une même catégorie, puis lesrule {}d'une policy dans leur ordre d'écriture (premier match = décision, comme un ACL classique). Ordre réel des 6 policies de ce dépôt :Ordre Policy Catégorie sequence_number1 DFW-InterZone-AdminInfrastructure 0 2 DFW-Zone-AdminApplication 20 3 DFW-Service-AppsApplication 25 4 DFW-ComplementsApplication 30 5 DFW-Zone-DMZApplication 40 6 DFW-Zone-SOCApplication 50 Visualisé comme une cascade de décision (premier match = ALLOW/DROP immédiat, sinon passage à la policy suivante) :
flowchart TD A(["Paquet évalué par le DFW distribué"]) --> B{"1. DFW-InterZone-Admin<br/>catégorie Infrastructure · seq 0"} B -->|match| Z(["Décision : ALLOW / DROP"]) B -->|pas de match| C{"2. DFW-Zone-Admin<br/>catégorie Application · seq 20"} C -->|match| Z C -->|pas de match| D{"3. DFW-Service-Apps<br/>catégorie Application · seq 25"} D -->|match| Z D -->|pas de match| E{"4. DFW-Complements<br/>catégorie Application · seq 30"} E -->|match| Z E -->|pas de match| F{"5. DFW-Zone-DMZ<br/>catégorie Application · seq 40<br/>deny-all scopé en fin de policy"} F -->|match| Z F -->|pas de match| G{"6. DFW-Zone-SOC<br/>catégorie Application · seq 50<br/>deny-all scopé en fin de policy"} G -->|match| Z G -->|pas de match| H(["Default Layer3 Rule : ALLOW<br/>(catch-all NSX, hors code)"]) -
La zone admin n'a pas de
deny-allscopé (contrairement à DMZ et SOC) : elle reste en allow par défaut. Les règles deDFW-Service-Apps(ex. accès à GLPI) sont donc posées pour la traçabilité/l'audit (logged = true), pas parce qu'elles sont strictement nécessaires au fonctionnement — un futur durcissement de la zone admin vers un modèle deny-all comme DMZ/SOC est anticipé dans les commentaires du code. - Piège SNAT FortiGate : tout accès management vers l'overlay est source-NATé par le FortiGate vers
10.15.91.14avant d'entrer dans NSX — c'est cette IP que voit la DFW dans toutes les règlesmgmt-to-*, pas l'IP réelle du poste administrateur. grp-zone-admin(groupe par tag VMzone|admin, critèrecondition) a 0 membre réel en pratique (constaté le 2026-07-05) : le tag n'est jamais appliqué aux VM. Les règles qui doivent cibler « toute la zone admin » utilisent donc des groupes par appartenance de segment (path_expression), plus fiables.- La règle
sauvegarde-to-aws-s3(dansDFW-Zone-Admin) autorise en sortie HTTPS le segmentseg-sauvegardevers Internet — c'est le chemin réseau qu'emprunte l'archivage vers le bucket S3 dédié (voir AWS). - Les objets NSX prédéfinis (~415 services, 64 profils système) ne sont pas dans le code : normal, ils sont exclus par choix et référencés par
pathuniquement.