OCS Inventory NG

Rôle

OCS Inventory NG est l'outil d'inventaire de parc (agents → serveur de communication → base de données) de l'infrastructure Indio, complémentaire à GLPI (qui embarque désormais son propre inventaire natif et sert de CMDB/ITSM de référence — voir GLPI). Redéployé le 2026-07-16 à la demande explicite du user malgré la note interne "GLPI remplace OCS" : le script Deploy-WorkstationBaseline.ps1 du projet ad-hardening déploie justement un agent OCS classique sur les postes, ce qui justifie un vrai serveur OCS indépendamment de GLPI.

Architecture

  • VM unique INDSERV026 (10.100.2.156), zone service (seg-service), 2 vCPU / 4 Go, clone du template Rocky 9.6 durci — IP allouée dynamiquement via NetBox. La VM d'origine (ocs/INDSERV001, provisionnée le 2026-07-04) a été détruite le 2026-07-07 dans le round de décommissionnement seg-service ; ce numéro 001 n'a volontairement pas été réutilisé (convention : ne jamais reprendre un numéro libéré tant que le segment reste partiellement occupé — voir Convention de nommage).
  • OCS Inventory NG 2.12.4, installé depuis le tarball officiel (setup.sh, dépôt Nexus ocs-tarball) — aucun paquet EL9 n'existe pour cette version.
  • Composants :
    • Serveur de communication (agents) + API REST : modules Apache httpd + mod_perl (Apache::Ocsinventory, Plack::Handler::Apache2) — aucun équivalent PHP-FPM/nginx-natif n'existe en branche 2.x, contrairement à GLPI. En écoute loopback uniquement (127.0.0.1:8080, patché depuis le défaut Listen 80).
    • Console ocsreports : PHP classique multi-fichiers (pas un front-controller unique comme GLPI), servie par PHP-FPM (pool dédié) derrière nginx.
    • nginx termine le TLS en public (443) et fait office de reverse proxy unique vers httpd (comm server/API, proxy_pass 127.0.0.1:8080) et vers PHP-FPM (console, socket unix).
  • MariaDB 10.11 locale, base ocsweb, utilisateur ocs (nom figé par les gabarits Apache livrés par OCS, jamais renommé) accessible à la fois en TCP 127.0.0.1 (mod_perl/DBD::mysql, pas de support socket unix côté Perl) et en socket unix (console PHP) — même bind-address par défaut couvre les deux, pas de skip-networking.
  • FQDN inventaire.infra.indio, certificat TLS Vault PKI (même pattern que GLPI, voir Procédure manuelle).

Structure du dépôt

Fichier / dossier Contenu
main.tf VM unique vsphere_virtual_machine.ocs, IP résolue via NetBox
ansible/site.yml Rôles mariadb puis ocs
ansible/roles/mariadb Base ocsweb (utf8mb3) + utilisateur ocs (localhost + 127.0.0.1)
ansible/roles/ocs/tasks/main.yml httpd/mod_perl, PHP-FPM, nginx, pilotage de setup.sh
ansible/roles/ocs/templates/setup.answers.j2 Réponses pré-remplies pour setup.sh
ansible/roles/ocs/templates/dbconfig.inc.php.j2 Remplace le fichier legacy généré par setup.sh
ansible/roles/ocs/templates/ocs-nginx.conf.j2 vhost nginx (double reverse proxy)
ansible/roles/ocs/files/tls/ Certificat/clé Vault PKI (statiques, non repris ici)

Chemin de requête (double pile applicative)

flowchart LR
    Agents["Agents OCS<br/>(postes)"] -->|"443 TLS"| Nginx["nginx public<br/>(443, Vault PKI)"]
    Admin(("Navigateur admin")) -->|"443 TLS<br/>/ocsreports"| Nginx

    Nginx -->|"/ocsreports/*.php<br/>fastcgi unix:/run/php-fpm/ocs.sock"| PHPFPM["php-fpm<br/>(pool dédié ocs)"]
    Nginx -->|"/ocsinventory /ocsapi /ocsinterface<br/>proxy_pass 127.0.0.1:8080"| Httpd["httpd + mod_perl<br/>(loopback uniquement)"]

    PHPFPM -->|"socket unix"| MariaDB[("MariaDB 10.11<br/>base ocsweb (utf8mb3)")]
    Httpd -->|"TCP 127.0.0.1<br/>DBD::mysql"| MariaDB

Provisioning Terraform

  • main.tf : VM unique vsphere_virtual_machine.ocs, IP résolue via NetBox.
  • Variables clés (variables.tf) : vm_name = INDSERV026, vsphere_network = seg-service, vm_cpu = 2, vm_ram = 4096.
  • Providers (versions.tf) : hashicorp/vsphere + e-breuninger/netbox.

Configuration Ansible

Playbook ansible/site.yml, deux rôles en séquence sur l'hôte ocs :

  1. mariadb — même schéma que GLPI (mot de passe root, nettoyage des comptes anonymes/test), mais base créée en utf8mb3/utf8mb3_general_ci (format du schéma OCS livré) et utilisateur ocs créé pour localhost et 127.0.0.1 (boucle sur les 2 hôtes).
  2. ocs — installe httpd/mod_perl et les dépendances Perl requises (perl-XML-Simple, perl-IO-Compress, perl-Apache-DBI, perl-Net-IP, perl-Mojolicious, perl-Plack, perl-Archive-Zip, perl-Switch, perl-YAML, perl-DBI, perl-DBD-MySQL — toutes disponibles en RPM EL9 via indio-epel/indio-crb, zéro module CPAN à compiler) ainsi que PHP-FPM/nginx ; télécharge le tarball OCS depuis Nexus (dépôt ocs-tarball) ; déploie setup.answers puis exécute setup.sh en mode piloté (yes "" | timeout 180 bash ./setup.sh) ; repatche le mot de passe DB dans les 2 fichiers de conf Apache générés ; désactive le vhost Apache de la console (remplacée par nginx/PHP-FPM) ; fait basculer httpd en écoute loopback 8080 ; importe le schéma SQL (ocsbase_new.sql) ; redéploie dbconfig.inc.php au bon format ; change le mot de passe admin (hash SHA-256) ; déploie le pool PHP-FPM, les certificats TLS et le vhost nginx ; corrige les permissions Unix (groupe apachenginx) ; active les contextes et booléens SELinux nécessaires.

Secrets (group_vars/ocs.yml, chiffrés ansible-vault) : mariadb_root_password, ocs_db_password, ocs_admin_new_password.

Procédure manuelle

Comme pour GLPI, le certificat TLS n'est pas émis par le rôle Ansible : préparé manuellement une fois via Vault PKI et copié dans roles/ocs/files/tls/ avant tout déploiement (vault write pki_int/issue/infra-indio common_name=inventaire.infra.indio ip_sans=10.100.2.156, ca_chain complet Root+Intermediate). Voir la procédure détaillée sur la page GLPI (identique).

Procédure de déploiement

  1. terraform apply → provisionne la VM.
  2. Préparer les certificats TLS sous roles/ocs/files/tls/ si pas déjà présents.
  3. ansible-playbook site.yml (tags mariadb, ocs).

Contrôle de santé / Vérification

Vérifications intégrées : la console ocsreports répond en HTTPS (200/302, retry jusqu'à 30 fois/5 s) ; l'endpoint /ocsinventory (serveur de communication, via proxy nginx → httpd loopback) répond.

systemctl status nginx php-fpm httpd mariadb
curl -sk -o /dev/null -w '%{http_code}\n' https://inventaire.infra.indio/ocsreports/

Points d'attention

setup.sh n'a aucun mode non interactif natif (~1600 lignes, 28 prompts bloquants). Onze pièges concrets, tous rencontrés et corrigés lors du déploiement du 2026-07-16, documentés en commentaires dans le rôle Ansible — à relire intégralement avant toute réinstallation ou montée de version :

  1. setup.answers — piège fondamental. Le fichier livré est commenté à 100 %. Sept variables (APACHE_BIN, APACHE_CONFIG_FILE, APACHE_USER, APACHE_GROUP, APACHE_CONFIG_DIRECTORY, PERL_BIN, MAKE) sont protégées côté script par if [ -z "$VAR" ]; then <auto-détection> fi. Les pré-remplir désactive l'auto-détection sans les renseigner : [ -d $VAR_VIDE ] dégénère en [ -d ] (test à un seul argument, toujours vrai côté POSIX) → boucle infinie sur le prompt correspondant. Ne jamais renseigner ces 7 variables ; seules celles sans logique d'auto-détection (DB_SERVER_*, chemins OCS_COM_SRV_*/ADM_SERVER_*, noms de fichiers conf) sont sûres à pré-remplir.
  2. Shebang #!/bin/sh. Sur Rocky 9, source fichier (sans /) sous /bin/sh échoue (le PATH ne contient pas .), alors que bash retombe silencieusement sur le répertoire courant. Toujours invoquer bash ./setup.sh, jamais ./setup.sh seul.
  3. Pas de pipefail sur la tâche yes "" | timeout 180 bash ./setup.sh. Une fois setup.sh terminé (exit 0), il ferme son stdin ; yes reçoit SIGPIPE (mort normale de ce pattern) et sort en 141. pipefail remonterait ce 141 comme un échec de toute la tâche malgré une installation réussie.
  4. Toutes les dépendances doivent être pré-installées avant de lancer setup.sh — sinon les prompts à défaut n (abandon) se déclenchent et bloquent, contrairement aux prompts y normalement traversés par yes "".
  5. dbconfig.inc.php écrit par setup.sh est un format legacy inutilisable ($_SESSION["SERVEUR_SQL"]=...) — le code runtime (backend/require/connexion.php) attend des define("DB_NAME", ...) (format normalement écrit par install.php via navigateur, qui code aussi en dur le mot de passe applicatif ocs/ocs). Le rôle écrit ce fichier lui-même via un template Ansible dédié plutôt que de passer par install.php.
  6. Le schéma DB n'est pas créé par setup.sh : import direct de ocsreports/files/ocsbase_new.sql (base neuve) — pas ocsbase.sql, qui sert uniquement aux migrations d'un schéma GUI_VERSION < 7006.
  7. Mot de passe DB applicatif ocs/ocs codé en dur dans les gabarits livrés ocsinventory-server.conf (PerlSetVar OCS_DB_PWD ocs) et ocsinventory-restapi.conf ($ENV{OCS_DB_PWD} = 'ocs';) — non substitué par setup.sh (contrairement à DATABASE_SERVER/DATABASE_PORT, qui eux le sont). Repatché après coup par 2 tâches ansible.builtin.replace ciblées (regex, garde le nom d'utilisateur ocs, ne change que le mot de passe).
  8. Mot de passe admin GUI admin/admin : table operators, colonne PASSWD. Le seed livré est en PASSWORD_VERSION=2, ce qui fait passer backend/AUTH/methode/local.php par hash(PASSWORD_CRYPT, $mdp) === $rowOp->PASSWD, où PASSWORD_CRYPT est défini en dur (ocsreports/var.php) comme 'sha256'pas bcrypt malgré le nom de la constante. UPDATE operators SET PASSWD = SHA2(<nouveau mdp>, 256) WHERE ID='admin' suffit, pas besoin d'invoquer PHP.
  9. Permissions Unix (pas SELinux) — le piège le plus long à diagnostiquer. setup.sh positionne tout en apache:apache (auto-détecté), mais la console est servie par nginx+PHP-FPM (utilisateur dédié nginx, pas membre du groupe apache) → Permission denied sur stat() côté nginx (ausearch -m avc ne montre rien, ce n'est pas SELinux) → dégénère en boucle de réécriture interne nginx (try_files rajoute /index.php indéfiniment car il n'arrive jamais à confirmer l'existence du fichier). Fix : chgrp -R nginx sur le dossier statique (/usr/share/ocsinventory-reports) et le dossier var (/var/lib/ocsinventory-reports, écrit par le code PHP lui-même) — propriétaire/mode inchangés, ils accordent déjà rx/rwx au groupe.
  10. SELinux (une fois les permissions Unix réglées, ce sont ensuite de vraies denials). /usr/share/ocsinventory-reports n'est pas labellisé par défaut (usr_t) → sefcontext + restorecon en httpd_sys_content_t (lecture) et /var/lib/ocsinventory-reports en httpd_sys_rw_content_t (écriture). Booléen httpd_can_network_connect requis pour que nginx proxifie vers 127.0.0.1:8080 — nginx tourne sous le même domaine SELinux httpd_t que httpd sur cette distribution ; sans ce booléen, name_connect refusé vers le port 8080 (labellisé http_cache_port_t) → 502 Bad Gateway côté client malgré httpd qui écoute correctement. httpd_can_network_connect_db + httpd_can_sendmail activés par précaution (comme GLPI).
  11. Certificat TLS Vault PKI : IP SAN nécessaire en plus du DNS SAN si la vérification se fait par IP plutôt que par nom — sans SAN IP, CERTIFICATE_VERIFY_FAILED: IP address mismatch. Émettre avec ip_sans=<ip> en plus de common_name. Utiliser ca_chain (tableau Root+Intermediate) de la réponse Vault, pas issuing_ca seul — un seul certificat intermédiaire ne suffit pas à bâtir la chaîne de confiance jusqu'au Root (unable to get issuer certificate sinon).

Autres points, hors du strict périmètre setup.sh :

  • Base créée en utf8mb3 (et non utf8mb4) car c'est le format du schéma livré (ocsbase_new.sql) — ne pas « moderniser » sans revalider la compatibilité du schéma OCS.
  • Utilisateur ocs accessible en TCP et socket unix (contrairement à GLPI, en socket unix seul) : nécessaire car DBD::mysql côté mod_perl n'a pas de support socket unix ici.
  • Certificat et clé TLS stockés sous roles/ocs/files/tls/ (contenu non repris dans cette documentation pour des raisons de sécurité).