Template doré tmpl-rocky96-hardened¶
Rôle¶
Le dépôt /root/packer (indio/iac/packer) construit avec HashiCorp Packer (builder
vsphere-iso) un template vCenter unique, tmpl-rocky96-hardened, à partir d'une image Rocky
Linux 9.6 minimale, durci (SELinux enforcing, firewalld zone drop par défaut, politique de mots
de passe et audit renforcés) et pré-intégré au dépôt interne Nexus.
Ce template est ensuite cloné par le dépôt Terraform /root/vmware pour produire chaque VM de la
flotte — voir vcenter-esxi.md.
Architecture¶
packer/
├── rocky9.pkr.hcl # source vsphere-iso + build (provisioners shell + manifest)
├── variables.pkr.hcl # variables
├── example.pkrvars.hcl # à copier en rocky96.pkrvars.hcl (secrets réels, jamais commité)
├── run-build.sh # wrapper de build (dérive le hash du mot de passe, init+validate+build)
├── http/
│ └── ks.pkrtpl.hcl # template Kickstart (rendu par templatefile(), hash injecté au build)
├── scripts/
│ ├── update.sh # mise à jour système + outils VMware
│ ├── hardening.sh # durcissement CIS-like
│ ├── internal-repo.sh # bootstrap du dépôt interne + installation du RPM indio-baseline-config
│ └── cleanup.sh # nettoyage avant conversion en template
└── manifest.json # historique des builds (artifact_id, uuid, date)
Le kickstart est livré via un ISO secondaire généré par Packer (cd_content, label OEMDRV)
plutôt que via un serveur HTTP : Anaconda détecte automatiquement ce device labellisé. Le
placement vSphere (datacenter/cluster/datastore) réutilise les mêmes objets que le dépôt vmware
(Datacenter/Cluster/vsanDatastore).
manifest.json montre plusieurs générations successives du template (tmpl-rocky96-hardened,
-v2, -v3) au fil des correctifs apportés au RPM indio-baseline-config embarqué ; seul le nom
canonique tmpl-rocky96-hardened doit être référencé par les dépôts consommateurs.
Schéma — pipeline de build¶
flowchart LR
ISO["ISO Rocky 9.6 minimal<br/>(datastore vCenter)"] --> BUILD
KS["http/ks.pkrtpl.hcl<br/>(templatefile, hash injecté)"] -->|ISO secondaire<br/>label OEMDRV| BUILD
subgraph BUILD["packer build (source vsphere-iso)"]
direction TB
B1["Boot + Anaconda<br/>(kickstart automatique)"] --> B2["update.sh<br/>(maj système + open-vm-tools)"]
B2 --> B3["hardening.sh<br/>(CIS-like : SSH, sysctl, auditd, firewalld...)"]
B3 --> B4["internal-repo.sh<br/>(bootstrap Nexus + indio-baseline-config)"]
B4 --> B5["cleanup.sh<br/>(purge logs/clés/machine-id, expect_disconnect)"]
end
BUILD --> CONV["convert_to_template = true<br/>remove_cdrom = true"]
CONV --> MANIFEST["manifest.json<br/>(artifact_id, uuid, date)"]
CONV --> TMPL[("tmpl-rocky96-hardened<br/>(vCenter)")]
TMPL -->|cloné par| TF["Terraform /root/vmware<br/>(voir vcenter-esxi.md)"]
Le kickstart (http/ks.pkrtpl.hcl)¶
Livré via un ISO secondaire généré par Packer (label OEMDRV, détecté automatiquement par
Anaconda) plutôt que par serveur HTTP. Contenu réel constaté :
- Réseau de build : IP statique dédiée
10.15.100.59/26, gateway10.15.100.62, hostnamerocky96-template.infra.indio— propre à l'environnement de build, sans rapport avec l'IP finale posée par Terraform à chaque clonage (bloccustomize, voir vcenter-esxi.md). - Comptes :
rootpw --iscryptedetuser --name=indio-adm --groups=wheel --iscryptedutilisent tous les deux le même hash${ssh_password_hash}, injecté partemplatefile()— dérivé automatiquement du mot de passe fourni àrun-build.sh(voir plus bas), jamais renseigné à la main. - Partitionnement durci (CIS-like), disque unique
sda(35 Go) :/bootet/boot/efihors LVM (nodev,nosuid,noexecsur/boot), puis un unique VGvg_systemavec des volumes logiques séparés pour/,swap,/home,/tmp,/var,/var/tmp,/var/log,/var/log/audit— chacun avec les options de montage durcies adaptées (nodev/nosuid/noexecselon le volume). ~1,5 Gio laissés libres dans le VG pour extension ultérieure. - Paquets (
%packages) :@^minimal-environment+@standard+open-vm-tools,cloud-init,cloud-utils-growpart,lvm2,chrony,audit,aide,rsyslog,firewalld, plus retrait explicite deiprutils/iwl*firmware/biosdevname/plymouth. %post: sudoers temporaire NOPASSWD pourindio-adm(retiré parcleanup.shen fin de build), import de la clé GPG Rocky, et overridedatasource_list: [VMware, NoCloud, OVF, None]danscloud-init— c'est ce qui permet à la fois la customization vSphere et le bloccustomizeTerraform de fonctionner correctement sur chaque clone.bootloader --append:audit=1 audit_backlog_limit=8192 slub_debug=P page_poison=1 vsyscall=none crashkernel=no— durcissement noyau au boot, en complément dehardening.sh.
Provisioning Packer¶
rocky9.pkr.hcl :
source "vsphere-iso" "rocky96": firmwareefi-secure, contrôleur disquepvscsi(thin provisionné), carte réseauvmxnet3, ISO Rocky 9.6 minimal fourni viaiso_paths. En fin de build :convert_to_template = true,remove_cdrom = true,tools_upgrade_policy = true.build "rocky96-hardened": deux provisionersshellexécutés dans l'ordre :update.sh,hardening.sh,internal-repo.sh(viasudo -S, mot de passe transmis en pipe depuisvar.ssh_password) ;cleanup.sh(expect_disconnect = true, la VM s'éteignant à la fin du script).post-processor "manifest": trace chaque build dansmanifest.json.
Variables clés (variables.pkr.hcl) : connexion vCenter (vsphere_password marquée sensitive),
placement, définition de la VM template (vm_disk_size par défaut 40960 Mo, nécessaire au
partitionnement LVM du kickstart), et le couple ssh_username/ssh_password/ssh_password_hash —
ce dernier est le hash sha512-crypt injecté dans le kickstart (rootpw/user du compte
indio-adm), à ne jamais renseigner à la main (voir procédure ci-dessous).
Étapes de durcissement (scripts/)¶
Ordre d'exécution réel et contenu vérifié dans scripts/ :
-
update.sh—dnf upgrade --refresh, installation deopen-vm-tools,cloud-init,cloud-utils-growpart,lvm2,chrony,audit,aide,firewalld; activation devmtoolsd. -
hardening.sh— durcissement CIS-like : - SSH : drop-in
/etc/ssh/sshd_config.d/00-hardening.conf(préfixe00-pour gagner le first-match face à un éventuel50-cloud-init.confgénéré ensuite) :PermitRootLogin no,PasswordAuthentication yes,PermitEmptyPasswords no,X11Forwarding no,MaxAuthTries 4, chiffrement restreint (Ciphers/MACs/KexAlgorithms modernes), bannière légale/etc/issue.net, validationsshd -tavant de continuer. - Politique de mots de passe :
pwquality(longueur minimale 14, 4 classes de caractères,maxrepeat 3, appliquée aussi à root) et vieillissement vialogin.defs(PASS_MAX_DAYS 90,UMASK 027). - Modules noyau désactivés :
cramfs,freevxfs,jffs2,hfs(+),squashfs,udf,usb-storage,dccp,sctp,rds,tipc. sysctlréseau/noyau durci : IP forwarding et redirects désactivés,rp_filter/syncookies/log_martiansactivés,dmesg_restrict/kptr_restrict/ptrace_scoperestreints, protections hardlinks/symlinks./dev/shmremonté avecnodev,nosuid,noexec.auditdactivé avec des règles de base (surveillance depasswd/shadow/group/sudoers/sshd_config, mode immuable-e 2).firewalld: service SSH autorisé dans la zonedropavant de définir cette zone comme zone par défaut (sinon la session SSH du build serait coupée) — c'est l'origine de la zonedroppar défaut du template.- Désactivation de
kdump.service. - Initialisation d'une base d'intégrité AIDE.
- Permissions durcies sur
/root(0700) etgrub.cfg(0600). -
FIPS : aucun appel à
fips-mode-setuptrouvé dans les scripts — le mode FIPS reste dans son état par défaut Rocky (désactivé), cohérent avec le "FIPS off" attendu du template. -
internal-repo.sh— bootstrap du dépôt interne : résoutrepo.infra.infravers la VIP HAProxy (10.100.2.130) dans/etc/hosts, crée un dépôt bootstrap minimalindio-custom, installe le RPMindio-baseline-config(dépôts Nexus/EPEL/CRB + NTP + proxy + CA Vault, voir baseline-config.md) puis retire le dépôt bootstrap (leindio.repodéfinitif à 6 sections est déposé par le RPM lui-même). -
cleanup.sh— nettoyage avant conversion en template : sudoers de build retiré, cache et logsdnfpurgés, logs applicatifs tronqués, clés SSH host supprimées (régénérées au premier boot de chaque clone),machine-idremis à zéro, connexions NetworkManager supprimées,cloud-init clean, historique bash purgé, remplissage de zéros pour améliorer la compression du disque thin-provisionné.
Configuration Ansible¶
Non applicable. Le provisioning post-boot du build (durcissement, dépôt interne) est fait par des
provisioners shell Packer natifs (scripts/*.sh, détaillés ci-dessus), pas par Ansible. La
configuration Ansible de la flotte intervient en aval, après clonage du template — voir
baseline-config.md.
Procédure de déploiement (build)¶
- Déposer l'ISO
Rocky-9.6-x86_64-minimal.isosur un datastore accessible. cp example.pkrvars.hcl rocky96.pkrvars.hclet adapter vCenter/placement/ISO (jamais le mot de passe dans ce fichier).- Lancer via le wrapper (méthode recommandée) :
PKR_VAR_ssh_password='...' ./run-build.sh rocky96.pkrvars.hcl
# ou saisie interactive masquée si la variable n'est pas fournie
VALIDATE_ONLY=1 ./run-build.sh rocky96.pkrvars.hcl # s'arrêter après validate
run-build.shdérive automatiquementssh_password_hash(openssl passwd -6) à partir du mot de passe fourni : le hash injecté dans le kickstart correspond ainsi toujours au mot de passe utilisé pour la connexion SSH du build (source de vérité unique, évite toute désynchronisation).packer init→packer validate→packer build.- Le template produit (
vm_name, par défauttmpl-rocky96-hardened) est ensuite consommé par/root/vmwarevia la variabletemplate_name. - Bascule du template canonique (manuelle, après un nouveau build réussi sous un nom
temporaire type
-v3) : renommage vCenter pour quetmpl-rocky96-hardenedpointe sur le nouveau build — procédure détaillée dans vcenter-esxi.md.
Contrôle de santé / Vérification¶
# Historique des builds (dates, uuid Packer)
cat manifest.json
# Confirmer le template présent côté vCenter (nom canonique, powerState off attendu)
govc vm.info tmpl-rocky96-hardened
Vérifié en direct le 2026-07-19 : tmpl-rocky96-hardened présent (poweredOff, hébergé sur
esxi-node01), cohérent avec manifest.json (dernier artifact_id = tmpl-rocky96-hardened-v3,
renommé depuis en nom canonique — voir Points d'attention sur les générations successives).
Points d'attention¶
rocky96.pkrvars.hcl(et tout*.pkrvars.hclhorsexample) ainsi que les logs de build (*.log) sont gitignorés : ils peuvent contenir des identifiants vCenter réels ou des sorties de commande sensibles — ne jamais les committer ni les citer dans une documentation.- Le mot de passe des comptes
root/indio-admn'est jamais stocké dans un fichier versionné : il transite par variable d'environnement ou saisie interactive, et le hash sha512-crypt est régénéré à chaque build — la valeur elle-même est gérée séparément (Vault / non versionnée). - Ordre critique dans
hardening.sh: autoriser SSH dans la zonedropde firewalld avant de la définir comme zone par défaut, sous peine de couper la session SSH du build en cours. - Le kickstart fixe une IP de build statique dédiée au réseau de build, distincte de l'IP finale
posée par Terraform à chaque clonage (bloc
customize) — normal, propre à l'environnement de build. - Plusieurs générations de template ont coexisté (
tmpl-rocky96-hardened,-v2,-v3) au fil des correctifs du RPMindio-baseline-config(dépôts stock Rocky d'abord désactivés puis supprimés) ; seul le nom canoniquetmpl-rocky96-hardeneddoit être utilisé par les projets consommateurs. PasswordAuthentication yesest activé sur le template : cohérent avec les provisioners Terraform du dépôtvmwarequi s'appuient sur une authentification SSH par mot de passe, à réévaluer si une politique clé-uniquement est souhaitée à terme.