Masquer les packs deja importes dans la fenetre d'import
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (push) Successful in 9m52s
build / Garde-fou (push) Successful in 10m41s
build / Images Harbor (web, Dockerfile) (push) Successful in 13m15s

This commit is contained in:
hcornet committed 2026-09-11 19:32:39 +02:00
1 parent 2187a1971e
commit ec195cac8f
71 files changed
+796 -30

No files matched your search

+39
View File
@@ -3,6 +3,45 @@
Format : [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
Versionnement sémantique, comme décrit dans [docs/CONVENTIONS.md](docs/CONVENTIONS.md).
## 1.15.0
### Modifié
- Les packs déjà présents dans le projet sont masqués de la fenêtre d'import, avec un
compteur et un lien pour les afficher : les reproposer n'avait aucun effet, l'import
les ignorait
## 1.14.1
### Modifié
- Le nom affiché dans l'interface devient « Forge Centreon », avec pour baseline
« Générateur de configuration pour Centreon avec CLAPI ». Le projet, le dépôt, les images
et le paquet Python restent « Enclume ».
## 1.14.0
### Ajouté
- Page d'aide « Installer SNMP » : configuration de départ sécurisée pour Debian, Ubuntu,
RHEL, Windows et les équipements réseau, avec la vérification depuis le collecteur
- Valeurs standards proposées pour les modèles parents, la version SNMP, le répertoire des
plugins, le collecteur et la criticité
### Modifié
- Moins de saisie libre : chaque champ qui a des valeurs connues les propose désormais
## 1.13.0
### Ajouté
- Chaque pack livre son groupe d'hôtes : importer un pack donne désormais les commandes,
les modèles, le groupe, le calendrier, le périmètre de notification et les prérequis
- La commande de vérification, ses arguments et le fuseau horaire sur l'hôte, où ils ont
leur place — ils n'existaient que sur le modèle d'hôte
- Un hôte créé après l'import d'un pack rejoint automatiquement son groupe
### Modifié
- Le fuseau horaire se choisit dans une liste fermée, alimentée par la base du système :
une faute de frappe ne se voyait qu'à l'exécution sur le central
- La commande de vérification propose celles livrées par Centreon et celles du projet
## 1.12.0
### Ajouté
+6
View File
@@ -37,6 +37,10 @@ prerequis: # facultatif mais précieux
notes: |
Activer le plugin de management et créer un compte en lecture seule.
groupe_hote: # le groupe dans lequel ranger les hôtes
name: HG-App-RabbitMQ
alias: RabbitMQ
notification: # la chaîne d'alerte livrée avec le pack
perimetre: RabbitMQ
calendrier: 24x7 # 24x7 et Heures-Ouvrees sont reconnus
@@ -77,6 +81,8 @@ même valeur.
- **Ce qui est commun à l'hôte va sur l'hôte** : ports, comptes, options du plugin. Ce qui est
propre à une sonde va sur la sonde : seuils, filtres.
- **Aucun mot de passe réel.** Valeur vide et `is_password: true`.
- **Un groupe d'hôtes.** Nommé `HG-<Famille>-<Techno>`. Un hôte sans groupe devient
introuvable dès que le parc grandit, et les droits comme les filtres s'appuient dessus.
- **Une chaîne de notification.** Un pack qui supervise sans prévenir personne n'a pas
d'intérêt : `calendrier`, `groupe` et `impact` sont attendus. Choisissez `24x7` pour ce
dont la panne réveille quelqu'un, `Heures-Ouvrees` sinon. Les calendriers livrés par
+4
View File
@@ -78,6 +78,10 @@ python3 -m enclume.cli --projet mon-projet.json --out ./sortie
| `GET /api/packs` | catalogue |
| `GET /api/packs/<id>/projet` | un pack converti en objets de projet |
| `POST /api/packs/<id>/fusionner` | ajoute un pack au projet en cours |
| `GET /api/fuseaux` | fuseaux horaires connus du système |
| `GET /api/valeurs-standards` | modèles parents, versions SNMP, criticités |
| `GET /aide/snmp` | installer et sécuriser SNMP, par système |
| `GET /api/commandes-usuelles` | commandes de vérification livrées par Centreon |
| `POST /api/valider` | validation seule |
| `POST /api/generer` | validation et génération des quatre fichiers |
| `POST /telecharger` | archive zip |
+3
View File
@@ -13,6 +13,9 @@ host_template:
- {name: APACHEPROTO, value: "http", description: "Protocole (http ou https)"}
- {name: APACHEURLPATH, value: "/server-status/?auto", description: "Chemin de la page mod_status"}
- {name: APACHEEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-Apache
alias: Apache HTTP Server
notification:
perimetre: Apache HTTP Server
calendrier: 24x7
@@ -20,6 +20,9 @@ host_template:
- {name: ESUSERNAME, value: "", description: "Compte de supervision"}
- {name: ESPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: ESEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-Elasticsearch
alias: Elasticsearch
notification:
perimetre: Elasticsearch
calendrier: 24x7
+3
View File
@@ -13,6 +13,9 @@ host_template:
- {name: NGINXPROTO, value: "http", description: "Protocole (http ou https)"}
- {name: NGINXURLPATH, value: "/nginx_status", description: "Chemin de la page stub_status"}
- {name: NGINXEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-Nginx
alias: Nginx
notification:
perimetre: Nginx
calendrier: 24x7
+3
View File
@@ -21,6 +21,9 @@ host_template:
- {name: RABBITUSERNAME, value: "centreon", description: "Compte de supervision"}
- {name: RABBITPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: RABBITEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-RabbitMQ
alias: RabbitMQ
notification:
perimetre: RabbitMQ
calendrier: 24x7
+3
View File
@@ -20,6 +20,9 @@ host_template:
- {name: MONGOUSERNAME, value: "centreon", description: "Compte de supervision"}
- {name: MONGOPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: MONGOEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-MongoDB
alias: MongoDB
notification:
perimetre: MongoDB
calendrier: 24x7
+3
View File
@@ -22,6 +22,9 @@ prerequis:
droit VIEW SERVER STATE pour le compte de supervision.
verifications:
- "/usr/lib/centreon/plugins/centreon_mssql.pl --plugin=database::mssql::plugin --list-mode"
groupe_hote:
name: HG-App-DB-MSSQL
alias: Microsoft SQL Server
notification:
perimetre: Microsoft SQL Server
calendrier: 24x7
+3
View File
@@ -23,6 +23,9 @@ prerequis:
GRANT REPLICATION CLIENT, PROCESS, SELECT ON *.* TO 'centreon'@'%';
verifications:
- "/usr/lib/centreon/plugins/centreon_mysql.pl --plugin=database::mysql::plugin --list-mode"
groupe_hote:
name: HG-App-DB-MySQL
alias: MySQL / MariaDB
notification:
perimetre: MySQL / MariaDB
calendrier: 24x7
+3
View File
@@ -21,6 +21,9 @@ prerequis:
definir ORACLE_HOME et LD_LIBRARY_PATH pour l'utilisateur centreon-engine.
verifications:
- "/usr/lib/centreon/plugins/centreon_oracle.pl --plugin=database::oracle::plugin --list-mode"
groupe_hote:
name: HG-App-DB-Oracle
alias: Oracle Database
notification:
perimetre: Oracle Database
calendrier: 24x7
+3
View File
@@ -24,6 +24,9 @@ prerequis:
GRANT pg_monitor TO centreon;
verifications:
- "/usr/lib/centreon/plugins/centreon_postgres.pl --plugin=database::postgres::plugin --list-mode"
groupe_hote:
name: HG-App-DB-PostgreSQL
alias: PostgreSQL
notification:
perimetre: PostgreSQL
calendrier: 24x7
+3
View File
@@ -19,6 +19,9 @@ host_template:
- {name: REDISPORT, value: "6379", description: "Port d'ecoute"}
- {name: REDISPASSWORD, value: "", description: "Mot de passe", is_password: true}
- {name: REDISEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-Redis
alias: Redis
notification:
perimetre: Redis
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Dell-iDRAC-SNMP
alias: Serveur Dell (iDRAC SNMP)
notification:
perimetre: Serveur Dell (iDRAC SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-HP-Proliant-SNMP
alias: Serveur HP ProLiant (iLO SNMP)
notification:
perimetre: Serveur HP ProLiant (iLO SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Alcatel-OmniSwitch
alias: Switch Alcatel OmniSwitch (SNMP)
notification:
perimetre: Switch Alcatel OmniSwitch (SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Aruba-Instant
alias: Bornes wifi Aruba Instant (SNMP)
notification:
perimetre: Bornes wifi Aruba Instant
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Aruba-SNMP
alias: Switch Aruba (SNMP)
notification:
perimetre: Switch Aruba (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Cisco-SNMP
alias: Switch / routeur Cisco (SNMP)
notification:
perimetre: Switch / routeur Cisco (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Cisco-WLC
alias: Controleur wifi Cisco WLC (SNMP)
notification:
perimetre: Controleur wifi Cisco WLC
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Dell-PowerConnect
alias: Switch Dell PowerConnect et N-Series (SNMP)
notification:
perimetre: Switch Dell PowerConnect et N-Series (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Extreme-SNMP
alias: Switch Extreme Networks (SNMP)
notification:
perimetre: Switch Extreme Networks (SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Fortinet-FortiGate-SNMP
alias: Firewall Fortinet FortiGate (SNMP)
notification:
perimetre: Firewall Fortinet FortiGate (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-HP-Procurve-SNMP
alias: Switch HP ProCurve et Aruba (SNMP)
notification:
perimetre: Switch HP ProCurve et Aruba (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Huawei-SNMP
alias: Switch et routeur Huawei (SNMP)
notification:
perimetre: Switch et routeur Huawei (SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Juniper-JunOS-SNMP
alias: Juniper JunOS (SNMP)
notification:
perimetre: Juniper JunOS (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-MikroTik-SNMP
alias: MikroTik RouterOS (SNMP)
notification:
perimetre: MikroTik RouterOS (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-PaloAlto-SNMP
alias: Pare-feu Palo Alto (SNMP)
notification:
perimetre: Pare-feu Palo Alto (SNMP)
calendrier: 24x7
+3
View File
@@ -16,6 +16,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-pfSense-SNMP
alias: Pare-feu pfSense (SNMP)
notification:
perimetre: Pare-feu pfSense (SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Ruckus-ZoneDirector
alias: Controleur wifi Ruckus ZoneDirector (SNMP)
notification:
perimetre: Controleur wifi Ruckus
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Stormshield-SNMP
alias: Pare-feu Stormshield (SNMP)
notification:
perimetre: Pare-feu Stormshield (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Ubiquiti-SNMP
alias: Ubiquiti UniFi et EdgeMax (SNMP)
notification:
perimetre: Ubiquiti UniFi et EdgeMax (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-OS-AIX-SNMP
alias: AIX (SNMP)
notification:
perimetre: AIX (SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-OS-FreeBSD-SNMP
alias: FreeBSD (SNMP)
notification:
perimetre: FreeBSD (SNMP)
calendrier: 24x7
@@ -19,6 +19,9 @@ prerequis:
notes: |
Cote client supervise : installer l'agent NRPE, declarer l'adresse du
collecteur dans allowed_hosts, et definir les commandes appelees.
groupe_hote:
name: HG-OS-Linux-NRPE
alias: Linux (NRPE)
notification:
perimetre: Linux (NRPE)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-OS-Linux-SNMP
alias: Linux (SNMP)
notification:
perimetre: Linux (SNMP)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-OS-Solaris-SNMP
alias: Solaris (SNMP)
notification:
perimetre: Solaris (SNMP)
calendrier: 24x7
@@ -13,6 +13,9 @@ host_template:
macros:
- {name: NSCLIENTPORT, value: "12489", description: "Port d'ecoute NSClient++"}
- {name: NSCLIENTPASSWORD, value: "", description: "Mot de passe NSClient++", is_password: true}
groupe_hote:
name: HG-OS-Windows-NSClient
alias: Windows (NSClient++ / check_nt)
notification:
perimetre: Windows (NSClient++ / check_nt)
calendrier: 24x7
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-OS-Windows-SNMP
alias: Windows (SNMP)
notification:
perimetre: Windows (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Printer-RFC3805
alias: Imprimante reseau (RFC 3805)
notification:
perimetre: Imprimante reseau (RFC 3805)
calendrier: Heures-Ouvrees
+3
View File
@@ -20,6 +20,9 @@ host_template:
- {name: LDAPBINDDN, value: "", description: "Compte de connexion (DN complet)"}
- {name: LDAPBINDPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: LDAPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-LDAP
alias: Annuaire LDAP
notification:
perimetre: Annuaire LDAP
calendrier: 24x7
+3
View File
@@ -9,6 +9,9 @@ host_template:
alias: Applicatif Protocoles reseau
parents: [generic-active-host-custom]
macros: []
groupe_hote:
name: HG-App-Protocols
alias: Services reseau standards (HTTP, TCP, DNS, NTP, SSH)
notification:
perimetre: Services reseau standards (HTTP, TCP, DNS, NTP, SSH)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-OS-Generic-SNMP
alias: Equipement generique (SNMP standard)
notification:
perimetre: Equipement generique (SNMP standard)
calendrier: 24x7
+3
View File
@@ -19,6 +19,9 @@ host_template:
- {name: UNITYUSERNAME, value: "centreon", description: "Compte de supervision"}
- {name: UNITYPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: UNITYEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-EMC-Unity
alias: Baie Dell EMC Unity (API REST)
notification:
perimetre: Baie Dell EMC Unity
calendrier: 24x7
+3
View File
@@ -19,6 +19,9 @@ host_template:
- {name: MSAUSERNAME, value: "monitor", description: "Compte de supervision"}
- {name: MSAPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: MSAEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-HP-MSA
alias: Baie HPE MSA et P2000 (API XML)
notification:
perimetre: Baie HPE MSA
calendrier: 24x7
+3
View File
@@ -18,6 +18,9 @@ host_template:
macros:
- {name: STORWIZEUSERNAME, value: "centreon", description: "Compte de supervision"}
- {name: STORWIZEEXTRAOPTIONS, value: "", description: "Options supplementaires, dont la cle SSH"}
groupe_hote:
name: HG-HW-IBM-Storwize
alias: Baie IBM Storwize et FlashSystem (SSH)
notification:
perimetre: Baie IBM Storwize
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-NetApp-ONTAP-SNMP
alias: Baie NetApp ONTAP (SNMP)
notification:
perimetre: Baie NetApp ONTAP (SNMP)
calendrier: 24x7
@@ -17,6 +17,9 @@ host_template:
macros:
- {name: PURETOKEN, value: "", description: "Jeton d'API", is_password: true}
- {name: PUREEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-PureStorage
alias: Baie Pure Storage (API REST)
notification:
perimetre: Baie Pure Storage
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-QNAP-SNMP
alias: NAS QNAP (SNMP)
notification:
perimetre: NAS QNAP
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-Synology-SNMP
alias: NAS Synology (SNMP)
notification:
perimetre: NAS Synology (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-APC-UPS-SNMP
alias: Onduleur APC (SNMP)
notification:
perimetre: Onduleur APC (SNMP)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
check_command_args: "!3!200,20%!400,50%"
macros:
- {name: SNMPEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-HW-UPS-RFC1628
alias: Onduleur standard (RFC 1628)
notification:
perimetre: Onduleur standard (RFC 1628)
calendrier: 24x7
+3
View File
@@ -12,6 +12,9 @@ host_template:
- {name: DOCKERPORT, value: "2376", description: "Port de l'API Docker"}
- {name: DOCKERPROTO, value: "https", description: "Protocole d'acces a l'API"}
- {name: DOCKEREXTRAOPTIONS, value: "", description: "Options supplementaires (certificats, api-version)"}
groupe_hote:
name: HG-App-Docker
alias: Docker (API REST)
notification:
perimetre: Docker (API REST)
calendrier: 24x7
+3
View File
@@ -21,6 +21,9 @@ host_template:
- {name: PVEUSERNAME, value: "centreon@pve", description: "Compte de supervision"}
- {name: PVEPASSWORD, value: "", description: "Mot de passe du compte", is_password: true}
- {name: PVEEXTRAOPTIONS, value: "", description: "Options supplementaires passees au plugin"}
groupe_hote:
name: HG-App-Proxmox
alias: Proxmox VE (API REST)
notification:
perimetre: Proxmox VE (API REST)
calendrier: 24x7
@@ -21,6 +21,9 @@ prerequis:
notes: |
Le connecteur Centreon VMware doit tourner et declarer un container
pointant sur le vCenter, avec un compte en lecture seule.
groupe_hote:
name: HG-App-VMware-ESX
alias: VMware ESXi (connecteur Centreon VMware)
notification:
perimetre: VMware ESXi (connecteur Centreon VMware)
calendrier: 24x7
+17
View File
@@ -43,6 +43,23 @@ Et enfin la revue humaine, qui ne s'automatise pas :
interpolation dans un ini.
- ce qui est écrit en base a-t-il traversé `strip_secrets` ?
## Un principe : le moins de saisie libre possible
Chaque champ de texte libre est une occasion de faute de frappe qui ne se verra qu'au
déploiement sur le central. Un champ nouveau doit donc, dans l'ordre de préférence :
1. **une liste fermée** quand les valeurs possibles sont connues et finies — un fuseau
horaire, un type de commande, une version SNMP ;
2. **une liste ouverte avec suggestions** quand des valeurs existent déjà sur le central que
nous ne connaissons pas — un modèle parent, un groupe d'hôtes, une commande de
vérification ;
3. **la saisie libre** seulement pour ce qui est propre à l'utilisateur : un nom, une
description, un seuil.
Et quand une valeur juste est majoritairement la même, elle est **préremplie**. Un projet
vide part avec `/usr/lib/centreon/plugins`, `generic-active-host-custom` et `Central` :
l'utilisateur corrige si son installation diffère, il ne devine pas.
## Nommage
| Objet | Règle | Exemple |
+2
View File
@@ -115,6 +115,7 @@ class Pack:
services: list[Service]
prerequis: dict[str, Any] = field(default_factory=dict)
notification: dict[str, Any] = field(default_factory=dict)
groupe_hote: dict[str, Any] = field(default_factory=dict)
source_file: str = ""
@property
@@ -149,6 +150,7 @@ class Pack:
services=[Service.from_dict(s) for s in raw.get("services") or []],
prerequis=raw.get("prerequis") or {},
notification=raw.get("notification") or {},
groupe_hote=raw.get("groupe_hote") or {},
source_file=source_file,
)
+7
View File
@@ -252,6 +252,13 @@ def build_plan(project: Project) -> Plan:
],
)
)
for param, valeur in (
("check_command", ref_command(host.check_command) if host.check_command else ""),
("check_command_arguments", host.check_command_args),
("timezone", host.timezone),
):
if valeur:
plan.ops.append(Op("HOST", "setparam", [host.name, param, valeur]))
for macro in host.macros:
plan.ops.append(Op("HOST", "setmacro", _macro_values(host.name, macro, fields)))
plan.ops.append(Op("HOST", "applytpl", [host.name]))
+21
View File
@@ -193,6 +193,9 @@ class Host:
templates: list[str] = field(default_factory=list)
poller: str = "Central"
hostgroups: list[str] = field(default_factory=list)
check_command: str = ""
check_command_args: str = ""
timezone: str = ""
macros: list[Macro] = field(default_factory=list)
@classmethod
@@ -205,6 +208,9 @@ class Host:
poller=_clean(raw.get("poller")) or "Central",
# Ancien format : une chaine unique separee par | ou par virgule.
hostgroups=_liste(raw.get("hostgroups")),
check_command=_clean(raw.get("check_command")),
check_command_args=_clean(raw.get("check_command_args")),
timezone=_clean(raw.get("timezone")),
macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
)
@@ -468,6 +474,16 @@ class Project:
# Validation
# --------------------------------------------------------------------------- #
def fuseaux_horaires() -> list[str]:
"""Fuseaux connus du systeme, au format attendu par Centreon."""
try:
import zoneinfo
return sorted(zoneinfo.available_timezones())
except Exception:
return []
def validate(project: Project) -> list[dict[str, str]]:
"""Retourne la liste des problemes, du bloquant au simple avertissement."""
@@ -532,6 +548,11 @@ def validate(project: Project) -> list[dict[str, str]]:
for htpl in project.host_templates:
_check_macros(htpl.macros, f"Modele d'hote {htpl.name}", issues)
connus = set(fuseaux_horaires())
for objet in (*project.host_templates, *project.hosts):
if objet.timezone and connus and objet.timezone.lstrip(":") not in connus:
error(f"{objet.name} : fuseau horaire inconnu, {objet.timezone}")
groupes = {g.name for g in project.groupes_hotes}
for host in project.hosts:
for groupe in host.hostgroups:
+12
View File
@@ -10,6 +10,7 @@ from .catalog import Pack
from .model import (
Calendrier,
Command,
GroupeHote,
HostTemplate,
Macro,
Perimetre,
@@ -76,6 +77,17 @@ def pack_to_project(
htpl.snmp_community = "public"
project.host_templates = [htpl]
# --- Groupe d'hotes livre avec le pack ----------------------------------
# Un hote sans groupe devient introuvable des que le parc grandit, et les
# droits comme les filtres s'appuient dessus. Le pack en propose donc un.
if pack.groupe_hote:
project.groupes_hotes = [
GroupeHote(
name=str(pack.groupe_hote.get("name") or ""),
alias=str(pack.groupe_hote.get("alias") or pack.name),
)
]
# --- Chaine de notification --------------------------------------------
notification = pack.notification or {}
perimetre_nom = ""
+176 -20
View File
@@ -173,7 +173,9 @@ function rendreRail() {
className: 'rail-reglages' + (selection.type === 'reglages' ? ' actif' : ''),
type: 'button',
textContent: 'Reglages du projet',
onclick: function () { selection = { type: 'reglages', index: 0 }; rendre(); }
onclick: function () { selection = { type: 'reglages', index: 0 }; chargerStandards();
rendre(); }
}));
Object.keys(LISTES).forEach(function (type) {
@@ -278,8 +280,8 @@ function rendreReglages(main) {
}));
var grille = elt('div', { className: 'grille' });
grille.appendChild(champ('Prefixe applique aux objets crees', projet, 'prefix', { placeholder: 'ex. ACME-' }));
grille.appendChild(champ('Repertoire des plugins', projet, 'plugins_dir'));
grille.appendChild(champ('Collecteur cible', projet, 'poller'));
grille.appendChild(champPropose('Repertoire des plugins', projet, 'plugins_dir', 'repertoires_plugins'));
grille.appendChild(champPropose('Collecteur cible', projet, 'poller', 'collecteurs'));
grille.appendChild(champ('Repertoire des scripts de notification', projet, 'notif_dir'));
main.appendChild(grille);
main.appendChild(caseACocher("Ajouter APPLYCFG a la fin des scripts", projet, 'apply_config'));
@@ -354,7 +356,7 @@ function rendreStpl(main, stpl) {
var grille = elt('div', { className: 'grille' });
grille.appendChild(champ('Nom du modele', stpl, 'name', { rail: true }));
grille.appendChild(champ('Alias', stpl, 'alias'));
grille.appendChild(champ('Modele parent', stpl, 'parent'));
grille.appendChild(champPropose('Modele parent', stpl, 'parent', 'modeles_service'));
grille.appendChild(champSelectListe('Commande de controle', stpl, 'command',
projet.commands.map(function (c) { return c.name; })));
main.appendChild(grille);
@@ -392,14 +394,15 @@ function rendreHtpl(main, htpl) {
var grille = elt('div', { className: 'grille' });
grille.appendChild(champ('Nom du modele', htpl, 'name', { rail: true, placeholder: 'OS-Linux-SNMP-custom' }));
grille.appendChild(champ('Alias', htpl, 'alias'));
grille.appendChild(champListe('Modeles parents', htpl, 'parents'));
grille.appendChild(champ('Fuseau horaire', htpl, 'timezone', { placeholder: ':Europe/Paris' }));
grille.appendChild(champListeSuggeree('Modeles parents', htpl, 'parents',
(STANDARDS && STANDARDS.modeles_hote) || ['generic-active-host-custom'], 'liste-modeles-hote'));
grille.appendChild(champFuseau('Fuseau horaire', htpl, 'timezone'));
main.appendChild(grille);
var grille2 = elt('div', { className: 'grille' });
grille2.appendChild(champ('Commande de verification', htpl, 'check_command', { placeholder: 'check_centreon_ping' }));
grille2.appendChild(champCommandeVerification('Commande de verification', htpl, 'check_command'));
grille2.appendChild(champ('Arguments', htpl, 'check_command_args', { placeholder: '!3!200,20%!400,50%' }));
grille2.appendChild(champ('Version SNMP', htpl, 'snmp_version', { placeholder: '2c' }));
grille2.appendChild(champPropose('Version SNMP', htpl, 'snmp_version', 'versions_snmp', { placeholder: '2c' }));
grille2.appendChild(champ('Communaute SNMP', htpl, 'snmp_community'));
main.appendChild(grille2);
@@ -426,7 +429,14 @@ function rendreHost(main, host) {
grille.appendChild(champ("Nom de l'hote", host, 'name', { rail: true }));
grille.appendChild(champ('Alias', host, 'alias'));
grille.appendChild(champ('Adresse IP ou DNS', host, 'address'));
grille.appendChild(champ('Collecteur', host, 'poller'));
grille.appendChild(champPropose('Collecteur', host, 'poller', 'collecteurs'));
grille.appendChild(champCommandeVerification('Commande de verification', host, 'check_command'));
grille.appendChild(champ('Arguments de la commande', host, 'check_command_args', { placeholder: '!3!200,20%!400,50%' }));
grille.appendChild(champFuseau('Fuseau horaire', host, 'timezone'));
if (!host.hostgroups.length && projet.groupes_hotes.length) {
// L'hote cree apres l'import d'un pack rejoint naturellement son groupe.
host.hostgroups = [projet.groupes_hotes[projet.groupes_hotes.length - 1].name];
}
grille.appendChild(champListeSuggeree("Groupes d'hotes", host, 'hostgroups',
projet.groupes_hotes.map(function (g) { return g.name; }), 'liste-groupes-hotes'));
main.appendChild(grille);
@@ -499,7 +509,7 @@ function rendrePerimetre(main, perimetre) {
var contexte = elt('div', { className: 'grille' });
contexte.appendChild(champ('Application', perimetre, 'application', { placeholder: 'Facturation' }));
contexte.appendChild(champ('Responsable', perimetre, 'responsable', { placeholder: 'Equipe finance' }));
contexte.appendChild(champ('Criticite', perimetre, 'criticite', { placeholder: 'Majeure' }));
contexte.appendChild(champPropose('Criticite', perimetre, 'criticite', 'criticites'));
contexte.appendChild(champ('Impact metier', perimetre, 'impact', { placeholder: 'Facturation client bloquee' }));
contexte.appendChild(champ('Procedure de reprise (URL)', perimetre, 'procedure', { placeholder: 'https://wiki/...' }));
contexte.appendChild(champ('Couleur du bandeau', perimetre, 'couleur', { placeholder: '#2f6f9f' }));
@@ -535,6 +545,108 @@ function rendrePerimetre(main, perimetre) {
main.appendChild(apercu);
}
var STANDARDS = null;
function chargerStandards() {
if (STANDARDS) return Promise.resolve(STANDARDS);
return fetch('/api/valeurs-standards').then(function (r) { return r.json(); })
.then(function (v) { STANDARDS = v || {}; return STANDARDS; })
.catch(function () { STANDARDS = {}; return STANDARDS; });
}
// Champ a valeurs proposees : la liste vient du serveur, la saisie reste
// possible pour ce qui existe deja sur un central particulier.
function champPropose(libelle, objet, cle, nomListe, options) {
options = options || {};
var label = elt('label', { className: 'field' });
label.appendChild(elt('span', { textContent: libelle }));
var identifiant = 'liste-' + nomListe;
var input = elt('input', { type: 'text', value: objet[cle] || '' });
if (options.placeholder) input.placeholder = options.placeholder;
input.setAttribute('list', identifiant);
input.addEventListener('input', function () { objet[cle] = input.value; sauver(); });
label.appendChild(input);
var datalist = elt('datalist', { id: identifiant });
label.appendChild(datalist);
chargerStandards().then(function (valeurs) {
(valeurs[nomListe] || []).forEach(function (v) {
datalist.appendChild(elt('option', { value: v }));
});
(options.enPlus || []).forEach(function (v) {
datalist.appendChild(elt('option', { value: v }));
});
});
return label;
}
var FUSEAUX = null;
var COMMANDES_USUELLES = null;
function chargerFuseaux() {
if (FUSEAUX) return Promise.resolve(FUSEAUX);
return fetch('/api/fuseaux').then(function (r) { return r.json(); })
.then(function (liste) { FUSEAUX = Array.isArray(liste) ? liste : []; return FUSEAUX; })
.catch(function () { FUSEAUX = []; return FUSEAUX; });
}
function chargerCommandesUsuelles() {
if (COMMANDES_USUELLES) return Promise.resolve(COMMANDES_USUELLES);
return fetch('/api/commandes-usuelles').then(function (r) { return r.json(); })
.then(function (liste) { COMMANDES_USUELLES = Array.isArray(liste) ? liste : []; return COMMANDES_USUELLES; })
.catch(function () { COMMANDES_USUELLES = []; return COMMANDES_USUELLES; });
}
// Champ ferme : la valeur est choisie dans une liste, jamais saisie. Un fuseau
// mal orthographie ne se voit qu'a l'execution sur le central.
function champFuseau(libelle, objet, cle) {
var label = elt('label', { className: 'field' });
label.appendChild(elt('span', { textContent: libelle }));
var select = elt('select');
select.appendChild(elt('option', { value: '', textContent: 'Celui du serveur central' }));
label.appendChild(select);
chargerFuseaux().then(function (liste) {
liste.forEach(function (fuseau) {
var option = elt('option', { value: fuseau, textContent: fuseau });
if (objet[cle] === fuseau) option.selected = true;
select.appendChild(option);
});
// Une valeur heritee d'un ancien projet doit rester visible.
if (objet[cle] && liste.indexOf(objet[cle]) === -1) {
var inconnu = elt('option', { value: objet[cle], textContent: objet[cle] + ' (inconnu)' });
inconnu.selected = true;
select.appendChild(inconnu);
}
});
select.addEventListener('change', function () { objet[cle] = select.value; sauver(); });
return label;
}
// Commande de verification : celles du projet et celles livrees par Centreon.
function champCommandeVerification(libelle, objet, cle) {
var label = elt('label', { className: 'field' });
label.appendChild(elt('span', { textContent: libelle }));
var input = elt('input', { type: 'text', value: objet[cle], placeholder: 'check_centreon_ping' });
input.setAttribute('list', 'liste-commandes-verification');
input.addEventListener('input', function () { objet[cle] = input.value; sauver(); });
label.appendChild(input);
var datalist = elt('datalist', { id: 'liste-commandes-verification' });
label.appendChild(datalist);
chargerCommandesUsuelles().then(function (liste) {
datalist.innerHTML = '';
liste.forEach(function (c) {
datalist.appendChild(elt('option', { value: c.nom, label: c.description }));
});
projet.commands.forEach(function (c) {
datalist.appendChild(elt('option', { value: c.name, label: 'commande du projet' }));
});
});
return label;
}
var MACROS_CENTREON = null;
function chargerMacros() {
@@ -1038,6 +1150,11 @@ function titreCatalogue(packs) {
return packs ? 'Importer un pack (' + packs.length + ')' : 'Importer un pack';
}
// Un pack est deja dans le projet si son modele d'hote y figure.
function packImporte(pack) {
return projet.host_templates.some(function (h) { return h.name === pack.host_template; });
}
function majTitreModale(texte) {
var titre = document.getElementById('modal-titre');
if (titre) titre.textContent = texte;
@@ -1047,11 +1164,33 @@ function ouvrirCataloguePacks() {
ouvrirModale(titreCatalogue(packsCache), function (corps) {
var recherche = elt('input', { type: 'text', placeholder: 'Filtrer par nom, technologie, plugin...' });
corps.appendChild(recherche);
var note = elt('div', { className: 'note-masques' });
corps.appendChild(note);
var resultats = elt('div', { className: 'packs' });
corps.appendChild(resultats);
// Les packs deja dans le projet sont masques : les reproposer n'aurait
// aucun effet, l'import les ignorerait.
var montrerImportes = false;
function afficher(packs) {
resultats.innerHTML = '';
note.innerHTML = '';
if (Array.isArray(packs) && !montrerImportes) {
var importes = packs.filter(packImporte);
packs = packs.filter(function (p) { return !packImporte(p); });
if (importes.length) {
note.appendChild(elt('span', {
textContent: importes.length + ' pack(s) deja dans le projet, masque(s). '
}));
note.appendChild(elt('button', {
type: 'button', className: 'small', textContent: 'Les afficher',
onclick: function () { montrerImportes = true; filtrer(); }
}));
}
}
if (Array.isArray(packs) && packsCache) {
majTitreModale(
packs.length === packsCache.length
@@ -1067,9 +1206,13 @@ function ouvrirCataloguePacks() {
return;
}
packs.forEach(function (pack) {
var ligne = elt('div', { className: 'pack-ligne' });
var deja = packImporte(pack);
var ligne = elt('div', { className: 'pack-ligne' + (deja ? ' importe' : '') });
var texte = elt('div');
texte.appendChild(elt('b', { textContent: pack.name }));
if (deja) {
texte.appendChild(elt('span', { className: 'tag ok', textContent: 'deja importe' }));
}
if (pack.status !== 'verified') {
texte.appendChild(elt('span', { className: 'tag warn', textContent: 'a valider' }));
}
@@ -1077,18 +1220,27 @@ function ouvrirCataloguePacks() {
texte.appendChild(elt('div', { className: 'plugin', textContent: pack.plugin + ' | ' + pack.services.length + ' sondes' }));
ligne.appendChild(texte);
var actions = elt('div', { className: 'barre' });
actions.appendChild(elt('button', {
type: 'button', className: 'small', textContent: 'Sondes par defaut',
onclick: function () { importerPack(pack.id, false); }
}));
actions.appendChild(elt('button', {
type: 'button', className: 'small', textContent: 'Toutes les sondes',
onclick: function () { importerPack(pack.id, true); }
}));
if (!deja) {
actions.appendChild(elt('button', {
type: 'button', className: 'small', textContent: 'Sondes par defaut',
onclick: function () { importerPack(pack.id, false); }
}));
actions.appendChild(elt('button', {
type: 'button', className: 'small', textContent: 'Toutes les sondes',
onclick: function () { importerPack(pack.id, true); }
}));
}
ligne.appendChild(actions);
resultats.appendChild(ligne);
});
if (!packs.length) resultats.appendChild(elt('p', { className: 'lede', textContent: 'Aucun pack ne correspond.' }));
if (!packs.length) {
resultats.appendChild(elt('p', {
className: 'lede',
textContent: montrerImportes
? 'Aucun pack ne correspond.'
: 'Aucun pack a importer ne correspond.'
}));
}
}
function filtrer() {
@@ -1193,6 +1345,10 @@ document.addEventListener('keydown', function (e) {
});
window.notifier = notifier;
// Lecture du projet en cours, pour le test d'interface et le diagnostic depuis
// la console. Une fonction plutot qu'une variable : le projet est remplace a
// chaque ouverture ou import.
window.projetCourant = function () { return projet; };
rendre();
+11
View File
@@ -593,3 +593,14 @@ pre.code {
@media (max-width: 900px) {
.bloc-ligne { grid-template-columns: 1fr 1fr; }
}
/* Packs deja dans le projet : visibles a la demande, sans action possible. */
.note-masques {
display: flex;
align-items: center;
gap: 8px;
color: var(--ink-dim);
font-size: 12.5px;
margin-bottom: 10px;
}
.pack-ligne.importe { opacity: 0.55; }
+3 -2
View File
@@ -1,15 +1,16 @@
{% extends "base.html" %}
{% block title %}Enclume — Générateur de configuration CLAPI pour Centreon{% endblock %}
{% block title %}Forge Centreon — Générateur de configuration pour Centreon avec CLAPI{% endblock %}
{% block body %}
<div class="accueil">
<section class="manchette">
<h2>Créez votre configuration Centreon, repartez avec le CLAPI</h2>
<p>Commandes de contrôle, modèles de service, modèles d'hôte : construisez-les dans votre
navigateur, à partir de zéro ou d'un des {{ pack_count }} packs du catalogue. Enclume produit
navigateur, à partir de zéro ou d'un des {{ pack_count }} packs du catalogue. Forge Centreon produit
les fichiers à jouer sur votre central — que vous relisez avant de les exécuter.</p>
<div class="barre">
<a class="btn primaire" href="/editeur">Ouvrir l'éditeur</a>
<a class="btn" href="/aide/snmp">Installer SNMP</a>
<a class="btn" href="#fonctionnement">Comment ça marche</a>
</div>
<p class="petit">Sans inscription. Rien n'est installé. Projet indépendant de Centreon SAS.</p>
+5 -4
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Enclume — Générateur de configuration CLAPI pour Centreon{% endblock %}</title>
<title>{% block title %}Forge Centreon — Générateur de configuration pour Centreon avec CLAPI{% endblock %}</title>
<meta name="description" content="Créez vos commandes de contrôle, modèles de service et modèles d'hôte Centreon, et repartez avec le code CLAPI prêt à jouer sur votre central. Gratuit, sans inscription.">
<link rel="canonical" href="{{ request.url_root }}">
<meta property="og:title" content="Enclume — Générateur de configuration CLAPI pour Centreon">
<meta property="og:title" content="Forge Centreon — Générateur de configuration pour Centreon avec CLAPI">
<meta property="og:description" content="Créez vos commandes, modèles de service et modèles d'hôte, repartez avec le code CLAPI.">
<meta property="og:type" content="website">
<link rel="icon" href="{{ url_for('static', filename='enclume.svg') }}" type="image/svg+xml">
@@ -14,14 +14,15 @@
</head>
<body>
<header class="topbar">
<h1><a href="/">Enclume</a></h1>
<span class="meta">Générateur de configuration CLAPI pour Centreon</span>
<h1><a href="/">Forge Centreon</a></h1>
<span class="meta">Générateur de configuration pour Centreon avec CLAPI</span>
<span class="version">{{ version }}</span>
</header>
{% block body %}{% endblock %}
<footer class="pied">
<span>Projet indépendant, sans lien avec Centreon SAS. Centreon est une marque déposée de son propriétaire.</span>
<span class="grow"></span>
<a href="/aide/snmp">Installer SNMP</a>
<a href="/mentions">Mentions légales</a>
<a href="{{ depot }}">Code source</a>
<a href="{{ depot }}/src/branch/main/CONTRIBUTING.md">Contribuer</a>
+143
View File
@@ -0,0 +1,143 @@
{% extends "base.html" %}
{% block title %}Installer et sécuriser SNMP — Forge Centreon{% endblock %}
{% block body %}
<div class="accueil texte">
<h2>Installer et sécuriser SNMP</h2>
<p>La plupart des packs du catalogue interrogent leurs équipements en SNMP. Cette page
donne, par système, une configuration de départ qui fonctionne et qui ne laisse pas votre
parc ouvert au premier venu.</p>
<div class="notice">
<b>Deux règles avant de commencer</b>
<p>SNMP v1 et v2c transportent la communauté <b>en clair</b> sur le réseau : quiconque
écoute peut la lire et interroger l'équipement. Préférez la <b>version 3</b> partout où
elle est disponible.</p>
<p style="margin-top:8px">Si vous restez en v2c — souvent le cas sur du matériel ancien —
restreignez au moins l'accès à l'adresse du collecteur et n'autorisez que la lecture.</p>
</div>
<h3>Debian et Ubuntu</h3>
<pre class="code">apt install snmpd</pre>
<p>Remplacez le contenu de <code>/etc/snmp/snmpd.conf</code> par ceci, en v3 :</p>
<pre class="code"># N'ecoute que sur l'interface d'administration, jamais sur toutes
agentaddress udp:10.0.4.20:161
# Ce que le compte a le droit de lire : tout l'arbre standard, rien d'autre
view lecture included .1.3.6.1.2.1
view lecture included .1.3.6.1.4.1
# Compte de supervision, lecture seule, sur la vue ci-dessus
rouser centreon authPriv -V lecture
# Informations d'inventaire, facultatives mais utiles
sysLocation Salle serveur, baie A3
sysContact [email protected]</pre>
<p>Le compte lui-même se crée service arrêté, la directive étant déplacée au démarrage
vers un fichier de persistance :</p>
<pre class="code">systemctl stop snmpd
echo 'createUser centreon SHA-256 "MotDePasseAuth" AES "MotDePasseChiffrement"' >> /etc/snmp/snmpd.conf
systemctl start snmpd</pre>
<div class="notice">
<b>Le piège classique</b>
<p>Après démarrage, la ligne <code>createUser</code> disparaît de
<code>/etc/snmp/snmpd.conf</code> : net-snmp la déplace dans
<code>/var/lib/snmp/snmpd.conf</code> sous forme chiffrée. C'est normal. Ne la remettez
pas, vous créeriez un doublon.</p>
</div>
<p>En v2c, si la version 3 n'est pas envisageable :</p>
<pre class="code">agentaddress udp:10.0.4.20:161
# Lecture seule, et uniquement depuis le collecteur
rocommunity UneChaineLongueEtAleatoire 10.0.4.10 -V lecture</pre>
<h3>RHEL, Alma et Rocky</h3>
<pre class="code">dnf install net-snmp net-snmp-utils</pre>
<p>Le fichier de configuration est au même endroit et la syntaxe identique. La différence
est le pare-feu :</p>
<pre class="code">firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \
source address="10.0.4.10/32" port port="161" protocol="udp" accept'
firewall-cmd --reload
systemctl enable --now snmpd</pre>
<p>Sur Debian et Ubuntu, l'équivalent avec ufw :</p>
<pre class="code">ufw allow from 10.0.4.10 to any port 161 proto udp</pre>
<h3>Windows</h3>
<p>Le service SNMP de Windows ne connaît que la v2c, et Microsoft le considère comme
obsolète. Il reste utilisable, mais pour un parc Windows important, les packs
<b>NSClient++</b> du catalogue sont un meilleur choix.</p>
<pre class="code"># Windows 10, 11, Server 2019 et suivants
Add-WindowsCapability -Online -Name "SNMP.Client~~~~0.0.1.0"
# Serveurs plus anciens
Install-WindowsFeature SNMP-Service -IncludeManagementTools</pre>
<p>La configuration se fait dans la base de registre. Communauté en lecture seule, et
restriction au seul collecteur :</p>
<pre class="code">$base = "HKLM:\SYSTEM\CurrentControlSet\Services\SNMP\Parameters"
# 4 = lecture seule. Ne jamais utiliser 8, qui autorise l'ecriture.
New-ItemProperty -Path "$base\ValidCommunities" -Name "UneChaineLongueEtAleatoire" `
-Value 4 -PropertyType DWord -Force
# Seul le collecteur a le droit d'interroger
New-ItemProperty -Path "$base\PermittedManagers" -Name "1" `
-Value "10.0.4.10" -PropertyType String -Force
Restart-Service SNMP</pre>
<p>Puis le pare-feu :</p>
<pre class="code">New-NetFirewallRule -DisplayName "SNMP depuis le collecteur" -Direction Inbound `
-Protocol UDP -LocalPort 161 -RemoteAddress 10.0.4.10 -Action Allow</pre>
<h3>Équipements réseau</h3>
<p>La logique est la même partout : un compte ou une communauté en lecture seule, une
liste d'accès limitée au collecteur, et la version 3 quand le matériel la gère.</p>
<pre class="code"># Cisco IOS, en v3
snmp-server view lecture iso included
snmp-server group SUPERVISION v3 priv read lecture access 20
snmp-server user centreon SUPERVISION v3 auth sha MotDePasseAuth priv aes 128 MotDePasseChiffrement
access-list 20 permit 10.0.4.10</pre>
<pre class="code"># Cisco IOS, en v2c
access-list 20 permit 10.0.4.10
snmp-server community UneChaineLongueEtAleatoire RO 20</pre>
<p>Sur pfSense, OPNsense, FortiGate, Stormshield et la plupart des interfaces web,
l'écran SNMP propose les mêmes notions : communauté ou utilisateur v3, interface
d'écoute, et adresses autorisées. Renseignez toujours la dernière.</p>
<h3>Vérifier depuis le collecteur</h3>
<p>Avant de déployer quoi que ce soit, assurez-vous que le collecteur obtient une réponse.
Une configuration Centreon parfaite ne sert à rien si le port est fermé.</p>
<pre class="code"># v3
snmpwalk -v3 -l authPriv -u centreon -a SHA-256 -A "MotDePasseAuth" \
-x AES -X "MotDePasseChiffrement" 10.0.4.20 sysDescr
# v2c
snmpwalk -v2c -c UneChaineLongueEtAleatoire 10.0.4.20 sysDescr</pre>
<p>Une réponse contenant la description du système signifie que tout est en place. Un
silence vient du pare-feu ou de l'adresse d'écoute ; un refus d'authentification, du
compte ou de la communauté.</p>
<div class="notice">
<b>Ce que Forge Centreon attend de vous ensuite</b>
<p>Les packs SNMP posent la version et la communauté sur le modèle d'hôte, dans les
réglages du projet. En version 3, laissez la communauté vide et passez les options
d'authentification par la macro <code>SNMPEXTRAOPTIONS</code> du modèle d'hôte.</p>
</div>
<p style="margin-top:24px"><a class="btn" href="/editeur">Retour à l'éditeur</a></p>
</div>
{% endblock %}
+2 -2
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Mentions légales — Enclume{% endblock %}
{% block title %}Mentions légales — Forge Centreon{% endblock %}
{% block body %}
<div class="accueil texte">
@@ -13,7 +13,7 @@
<p>{{ hebergeur or "Non renseigné — définir ENCLUME_HEBERGEUR." }}</p>
<h3>Marques</h3>
<p>Centreon est une marque déposée de Centreon SAS. Enclume est un projet indépendant, sans
<p>Centreon est une marque déposée de Centreon SAS. Forge Centreon est un projet indépendant, sans
lien ni affiliation avec cette société. Les noms de constructeurs et d'éditeurs cités dans le
catalogue appartiennent à leurs propriétaires respectifs.</p>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Moderation — Enclume{% endblock %}
{% block title %}Moderation — Forge Centreon{% endblock %}
{% block body %}
<div class="wrap-moderation">
{% if not connecte %}
+57
View File
@@ -121,6 +121,11 @@ def create_app() -> Flask:
verifies=sum(1 for p in cat.packs.values() if p.status == "verified"),
)
@app.get("/aide/snmp")
def guide_snmp() -> str:
"""Installer et securiser SNMP, par systeme."""
return render_template("guide-snmp.html")
@app.get("/mentions")
def mentions() -> str:
return render_template(
@@ -252,6 +257,58 @@ def create_app() -> Flask:
payload = request.get_json(force=True, silent=True) or {}
return jsonify(generate(payload.get("project", payload)))
@app.get("/api/fuseaux")
def api_fuseaux() -> Response:
"""Fuseaux horaires connus : la saisie libre menait a des erreurs muettes."""
from .model import fuseaux_horaires
return jsonify(fuseaux_horaires())
@app.get("/api/commandes-usuelles")
def api_commandes_usuelles() -> Response:
"""Commandes de verification livrees par Centreon."""
return jsonify(
[
{"nom": "check_centreon_ping", "description": "Ping, avec seuils de perte et de latence"},
{"nom": "check_host_alive", "description": "Ping simplifie, disponibilite de l'hote"},
{"nom": "check-host-alive", "description": "Variante historique de check_host_alive"},
{"nom": "check_centreon_dummy", "description": "Etat fixe, pour un hote passif"},
{"nom": "check_centreon_snmp_value", "description": "Valeur SNMP, pour un hote sans ping"},
]
)
@app.get("/api/valeurs-standards")
def api_valeurs_standards() -> Response:
"""Valeurs livrees par Centreon, proposees plutot que saisies.
Chaque champ libre est une occasion de faute de frappe qui ne se voit
qu'au moment du deploiement sur le central.
"""
return jsonify(
{
"modeles_hote": [
"generic-active-host-custom",
"generic-passive-host-custom",
"generic-active-host",
"generic-passive-host",
],
"modeles_service": [
"generic-active-service-custom",
"generic-passive-service-custom",
"generic-active-service",
"generic-passive-service",
],
"versions_snmp": ["1", "2c", "3"],
"repertoires_plugins": [
"/usr/lib/centreon/plugins",
"/usr/lib/nagios/plugins",
"/usr/local/centreon/plugins",
],
"criticites": ["Critique", "Majeure", "Mineure", "Information"],
"collecteurs": ["Central"],
}
)
@app.get("/api/macros-notification")
def api_macros_notification() -> Response:
"""Les macros proposees dans l'editeur de blocs."""
+2
View File
@@ -8,3 +8,5 @@ Flask-SQLAlchemy>=3.1,<4.0
SQLAlchemy>=2.0,<3.0
alembic>=1.13,<2.0
psycopg[binary]>=3.1,<4.0
# Base des fuseaux horaires : absente des images slim, la liste serait vide.
tzdata>=2024.1
+41 -1
View File
@@ -30,7 +30,11 @@ const dom = new JSDOM(html, {
const { window } = dom;
// Reponses minimales mais de la bonne forme, comme celles du serveur.
const reponses = {
'/api/packs': [],
'/api/packs': [
{ id: 'app-essai', name: 'Essai', category: 'applications', category_label: 'Applications',
status: 'verified', plugin: 'essai.pl', description: 'Pack d essai',
host_template: 'App-Essai-custom', services: [] },
],
'/api/generer': { summary: {}, issues: [], files: { 'deploy.clapi': '' } },
'/api/projets': [],
};
@@ -90,6 +94,42 @@ if (erreurs.length === 0) {
}
}
// Un pack deja dans le projet ne doit plus etre proposable a l'import.
try {
window.eval("projetCourant().host_templates.push({ name: 'App-Essai-custom', macros: [], parents: [] });");
document.getElementById('btn-pack').click();
await new Promise((r) => setTimeout(r, 30));
const corps = document.getElementById('modal-corps');
const boutons = [...corps.querySelectorAll('button')].map((b) => b.textContent);
if (boutons.includes('Sondes par defaut')) {
erreurs.push('un pack deja importe reste proposable');
}
if (!corps.textContent.includes('deja dans le projet')) {
erreurs.push('le masquage des packs importes n est pas signale');
}
// Affiches a la demande, ils restent sans action : les reimporter n'aurait
// aucun effet, et un bouton inerte ferait douter l'utilisateur.
const afficher = [...corps.querySelectorAll('button')]
.find((b) => b.textContent === 'Les afficher');
if (!afficher) {
erreurs.push('aucun moyen d afficher les packs masques');
} else {
afficher.click();
await new Promise((r) => setTimeout(r, 30));
const apres = [...corps.querySelectorAll('button')].map((b) => b.textContent);
if (apres.includes('Sondes par defaut')) {
erreurs.push('un pack deja importe redevient proposable une fois affiche');
}
if (!corps.textContent.includes('deja importe')) {
erreurs.push('un pack affiche n est pas signale comme deja importe');
}
}
document.querySelector('[data-fermer-modal]').click();
} catch (erreur) {
erreurs.push('catalogue de packs : ' + erreur.message);
}
// Les notifications doivent s'afficher, avec leur type.
try {
window.eval("notifier('essai de reussite', 'succes'); notifier('essai d erreur', 'erreur');");
+91
View File
@@ -208,3 +208,94 @@ def test_rollback_supprime_les_groupes_d_hotes():
projet = Project.from_dict({"groupes_hotes": [{"name": "HG-Essai"}]})
lignes = [o.import_line() for o in build_plan(projet).rollback_ops]
assert "HG;DEL;HG-Essai" in lignes
def test_chaque_pack_livre_son_groupe_d_hotes():
"""Un utilisateur qui importe un pack doit recevoir une configuration complete."""
from enclume.packs import pack_to_project
catalogue = Catalog("catalog")
sans = [p.id for p in catalogue.packs.values() if not p.groupe_hote]
assert not sans, f"packs sans groupe d'hotes : {sans}"
projet = pack_to_project(catalogue.get("app-mysql"))
assert [g.name for g in projet.groupes_hotes] == ["HG-App-DB-MySQL"]
lignes = [o.import_line() for o in build_plan(projet).ops if o.obj == "HG"]
assert lignes == ["HG;ADD;HG-App-DB-MySQL;MySQL / MariaDB"]
def test_commande_et_fuseau_sur_l_hote():
"""La commande de verification et le fuseau appartiennent a l'hote."""
projet = Project.from_dict(
{
"hosts": [
{
"name": "srv-01",
"address": "10.0.4.1",
"check_command": "check_centreon_ping",
"check_command_args": "!3!200,20%!400,50%",
"timezone": "Europe/Paris",
}
]
}
)
lignes = [o.import_line() for o in build_plan(projet).ops if o.obj == "HOST"]
assert "HOST;setparam;srv-01;check_command;check_centreon_ping" in lignes
assert "HOST;setparam;srv-01;check_command_arguments;!3!200,20%!400,50%" in lignes
assert "HOST;setparam;srv-01;timezone;Europe/Paris" in lignes
def test_fuseau_horaire_errone_refuse():
"""Une faute de frappe sur un fuseau ne se verrait qu'a l'execution."""
from enclume.model import fuseaux_horaires, validate
if not fuseaux_horaires():
return # base de fuseaux absente de la machine
projet = Project.from_dict(
{"hosts": [{"name": "srv", "address": "10.0.0.1", "timezone": "Europe/Pariss"}]}
)
erreurs = [i["message"] for i in validate(projet) if i["level"] == "error"]
assert any("fuseau horaire inconnu" in m for m in erreurs)
def test_les_fuseaux_sont_exposes():
client = create_app().test_client()
fuseaux = client.get("/api/fuseaux").get_json()
assert "Europe/Paris" in fuseaux
commandes = [c["nom"] for c in client.get("/api/commandes-usuelles").get_json()]
assert "check_centreon_ping" in commandes
def test_guide_snmp_accessible_et_complet():
"""Une page d'aide qui n'aborde pas la securite ferait plus de mal que de bien."""
page = create_app().test_client().get("/aide/snmp").data.decode()
for systeme in ("Debian", "RHEL", "Windows", "Cisco"):
assert systeme in page, systeme
# Les points de securite qui distinguent une configuration tenable.
for notion in ("authPriv", "rocommunity", "PermittedManagers", "snmpwalk", "firewall-cmd"):
assert notion in page, notion
# La lecture seule, jamais l'ecriture.
assert "Ne jamais utiliser 8" in page
def test_valeurs_standards_exposees():
"""Les listes proposees remplacent la saisie libre : elles doivent exister."""
valeurs = create_app().test_client().get("/api/valeurs-standards").get_json()
assert "generic-active-host-custom" in valeurs["modeles_hote"]
assert "generic-active-service-custom" in valeurs["modeles_service"]
assert valeurs["versions_snmp"] == ["1", "2c", "3"]
assert "/usr/lib/centreon/plugins" in valeurs["repertoires_plugins"]
assert "Critique" in valeurs["criticites"]
def test_nom_affiche_et_baseline():
"""Le nom affiche est « Forge Centreon » ; le projet et les images restent « Enclume »."""
page = create_app().test_client().get("/").data.decode()
assert "Forge Centreon" in page
assert "Générateur de configuration pour Centreon avec CLAPI" in page
# La mention d'independance devient d'autant plus necessaire.
assert "Centreon SAS" in page