feat(templates): Resolve cross-template collisions and validation

- exchange: Replaced 'service.discovery' and 'service.info' with WMI to avoid conflict with Windows template.
- exchange: Added JS preprocessing for Service State mapping.
- windows: Fixed missing 'ZABBIX_ACTIVE' type on RDP, Disk Queue, and Security Audit items.
- tools: Updated 'validate_zabbix_template.py' to detect reserved keys and Active Agent compliance.
- docs: Regenerated documentation for both templates.
This commit is contained in:
João Pedro Toledo Goncalves 2026-01-13 13:40:12 -03:00
parent 0bc4984f89
commit b74b96473c
4 changed files with 117 additions and 16 deletions

View File

@ -277,33 +277,35 @@ zabbix_export:
- uuid: c3c2d45be1f4439292678d07d36eb042 - uuid: c3c2d45be1f4439292678d07d36eb042
name: Descoberta de Serviços Exchange name: Descoberta de Serviços Exchange
type: ZABBIX_ACTIVE type: ZABBIX_ACTIVE
key: service.discovery key: wmi.getall[root\cimv2,"SELECT Name, DisplayName, State FROM Win32_Service WHERE Name LIKE 'MSExchange%' AND StartMode='Auto'"]
delay: 1h delay: 1h
filter: description: Descobre serviços do Exchange que iniciam automaticamente via WMI (evita conflito com Template Windows).
evaltype: AND lld_macro_paths:
conditions: - lld_macro: '{#SERVICE.NAME}'
- macro: '{#SERVICE.NAME}' path: $.Name
value: ^(?!MSExchangeTransport$|MSExchangeSubmission$)MSExchange.* - lld_macro: '{#SERVICE.DISPLAYNAME}'
operator: MATCHES_REGEX path: $.DisplayName
- macro: '{#SERVICE.STARTUPNAME}'
value: automatic
operator: MATCHES_REGEX
description: Descobre serviços do Exchange que iniciam automaticamente.
item_prototypes: item_prototypes:
- uuid: dd3c489ce5f94540b50b6f2aa1aac65e - uuid: dd3c489ce5f94540b50b6f2aa1aac65e
name: 'Serviço: Status do {#SERVICE.DISPLAYNAME}' name: 'Serviço: Status do {#SERVICE.DISPLAYNAME}'
type: ZABBIX_ACTIVE type: ZABBIX_ACTIVE
key: service.info["{#SERVICE.NAME}",state] key: wmi.get[root\cimv2,"SELECT State FROM Win32_Service WHERE Name='{#SERVICE.NAME}'"]
delay: 3m delay: 3m
description: Status atual do serviço {#SERVICE.DISPLAYNAME}. description: Status atual do serviço {#SERVICE.DISPLAYNAME} via WMI.
valuemap: valuemap:
name: Service state name: Service state
preprocessing:
- type: JAVASCRIPT
parameters:
- |
var states = {'Running': 0, 'Paused': 1, 'Start Pending': 2, 'Pause Pending': 3, 'Continue Pending': 4, 'Stop Pending': 5, 'Stopped': 6};
return states[value] !== undefined ? states[value] : 7;
tags: tags:
- tag: component - tag: component
value: service value: service
trigger_prototypes: trigger_prototypes:
- uuid: 38cefb16c51448d69e05b322e08a495a - uuid: 38cefb16c51448d69e05b322e08a495a
expression: last(/Microsoft Exchange Server 2016 by Zabbix agent active/service.info["{#SERVICE.NAME}",state])<>0 expression: last(/Microsoft Exchange Server 2016 by Zabbix agent active/wmi.get[root\cimv2,"SELECT State FROM Win32_Service WHERE Name='{#SERVICE.NAME}'"])<>0
name: '🚨 Serviço Parado: {#SERVICE.DISPLAYNAME}' name: '🚨 Serviço Parado: {#SERVICE.DISPLAYNAME}'
priority: HIGH priority: HIGH
description: O serviço {#SERVICE.DISPLAYNAME} deveria estar rodando (Auto) mas está parado. description: O serviço {#SERVICE.DISPLAYNAME} deveria estar rodando (Auto) mas está parado.

View File

@ -34,9 +34,9 @@ Desenvolvido por Arthur "O Farol" Mendes.
### Regras de Descoberta (LLD) ### Regras de Descoberta (LLD)
#### Descoberta de Serviços Exchange (`service.discovery`) #### Descoberta de Serviços Exchange (`wmi.getall[root\cimv2,"SELECT Name, DisplayName, State FROM Win32_Service WHERE Name LIKE 'MSExchange%' AND StartMode='Auto'"]`)
- **Protótipos de Itens:** - **Protótipos de Itens:**
- Serviço: Status do {#SERVICE.DISPLAYNAME} (`service.info["{#SERVICE.NAME}",state]`) - Serviço: Status do {#SERVICE.DISPLAYNAME} (`wmi.get[root\cimv2,"SELECT State FROM Win32_Service WHERE Name='{#SERVICE.NAME}'"]`)
#### Descoberta de Bancos de Dados (`perf_instance.discovery["MSExchange Active Manager"]`) #### Descoberta de Bancos de Dados (`perf_instance.discovery["MSExchange Active Manager"]`)
- **Protótipos de Itens:** - **Protótipos de Itens:**
- Banco [{#INSTANCE}]: Função da Cópia (`perf_counter_en["\MSExchange Active Manager({#INSTANCE})\Database Copy Role Active"]`) - Banco [{#INSTANCE}]: Função da Cópia (`perf_counter_en["\MSExchange Active Manager({#INSTANCE})\Database Copy Role Active"]`)

View File

@ -545,6 +545,7 @@ zabbix_export:
- uuid: ab5cc8d3c6734c7c9456c02dfecffd5d - uuid: ab5cc8d3c6734c7c9456c02dfecffd5d
name: 'RDP: Sessões Ativas (Total)' name: 'RDP: Sessões Ativas (Total)'
key: perf_counter_en["\Terminal Services\Total Sessions"] key: perf_counter_en["\Terminal Services\Total Sessions"]
type: ZABBIX_ACTIVE
delay: 1m delay: 1m
value_type: FLOAT value_type: FLOAT
units: '' units: ''
@ -563,6 +564,7 @@ zabbix_export:
description: Existem muitas sessões de terminal abertas. Isso pode consumir recursos ou indicar sessões "penduradas". description: Existem muitas sessões de terminal abertas. Isso pode consumir recursos ou indicar sessões "penduradas".
- uuid: 44cf725b9c03464cac04df1103f51092 - uuid: 44cf725b9c03464cac04df1103f51092
name: 'Disco: Tamanho da Fila (Queue Length)' name: 'Disco: Tamanho da Fila (Queue Length)'
type: ZABBIX_ACTIVE
key: perf_counter_en["\PhysicalDisk(_Total)\Current Disk Queue Length"] key: perf_counter_en["\PhysicalDisk(_Total)\Current Disk Queue Length"]
delay: 1m delay: 1m
value_type: FLOAT value_type: FLOAT
@ -581,6 +583,7 @@ zabbix_export:
description: A fila de disco está constantemente alta. O armazenamento não está dando conta das requisições. description: A fila de disco está constantemente alta. O armazenamento não está dando conta das requisições.
- uuid: 141cc10cfa0b40c0a00e4737a965ef87 - uuid: 141cc10cfa0b40c0a00e4737a965ef87
name: 'Segurança: Falhas de Login (Audit Failure)' name: 'Segurança: Falhas de Login (Audit Failure)'
type: ZABBIX_ACTIVE
key: eventlog[Security,,,,4625] key: eventlog[Security,,,,4625]
delay: 1m delay: 1m
value_type: LOG value_type: LOG

View File

@ -322,10 +322,88 @@ def validate_zabbix_7_compliance(content):
for i, item in enumerate(node): for i, item in enumerate(node):
check_node(item, f"{path}[{i}]") check_node(item, f"{path}[{i}]")
check_node(content)
check_node(content) check_node(content)
return errors, warnings return errors, warnings
def validate_reserved_keys(content):
"""
Check for keys that are known to cause collisions or are reserved/monopolistic.
e.g. service.discovery, service.info with generic params.
"""
warnings = []
def check_keys(node, path="root"):
if isinstance(node, dict):
if 'key' in node:
key = node['key']
# Check for service.discovery (without unique params, it collides generally)
if key == 'service.discovery':
warnings.append(f"[POTENTIAL COLLISION] Key 'service.discovery' found at {path}. This often conflicts with Windows OS templates. Consider 'wmi.getall'.")
if str(key).startswith('service.info'):
warnings.append(f"[POTENTIAL COLLISION] Key '{key}' found at {path}. Ensure this does not conflict with standard Windows templates. Consider 'wmi.get'.")
for k, v in node.items():
check_keys(v, f"{path}.{k}")
elif isinstance(node, list):
for i, item in enumerate(node):
check_keys(item, f"{path}[{i}]")
check_keys(content)
return warnings
def validate_active_agent_compliance(content):
"""
If template name suggests 'Active', ensure items have type: ZABBIX_ACTIVE.
"""
errors = []
template_name = ""
# Extract template name
if isinstance(content, dict) and 'zabbix_export' in content:
if 'templates' in content and isinstance(content['templates'], list) and len(content['templates']) > 0:
template_name = content['templates'][0].get('name', '')
# Check case-insensitive 'active' in template name
if 'active' not in template_name.lower():
return errors # Not an active agent template candidate, skip check
def check_items(node, path="root"):
if isinstance(node, dict):
# Check if it looks like an item/prototype (has 'key' and 'name' and 'uuid')
# Avoiding false positives on non-item dicts
if 'key' in node and 'name' in node and 'type' in node:
item_type = node.get('type')
key = node['key']
# List of types that are NOT Agent checks (so don't need ZABBIX_ACTIVE)
# ZABBIX_PASSIVE is omitted because that's exactly what we want to FLAG if found.
allowed_non_agent_types = [
'ZABBIX_ACTIVE',
'SIMPLE', 'SNMP', 'INTERNAL', 'TRAP', 'EXTERNAL',
'DB_MONITOR', 'HTTP_AGENT', 'SSH', 'TELNET', 'CALCULATED', 'JMX', 'DEPENDENT', 'SCRIPT', 'BROWSER'
]
# If type is invalid or missing (None), or explicitly Passive (ZABBIX_PASSIVE/0)
if item_type not in allowed_non_agent_types:
# If it turns out to be Passive (which is default export if missing, or explicit)
errors.append(f"[ACTIVE AGENT MISMATCH] Item '{node['name']}' ({key}) at {path} is not ZABBIX_ACTIVE (found: '{item_type}'). Template '{template_name}' is marked Active.")
# Recurse
for k, v in node.items():
check_items(v, f"{path}.{k}")
elif isinstance(node, list):
for i, item in enumerate(node):
check_items(item, f"{path}[{i}]")
check_items(content)
return errors
def check_duplicate_yaml_keys(file_path): def check_duplicate_yaml_keys(file_path):
""" """
@ -510,6 +588,24 @@ def validate_yaml(file_path):
if z7_warnings: if z7_warnings:
warnings.extend(z7_warnings) warnings.extend(z7_warnings)
# ========== 8. Reserved Keys/Collision Check ==========
print("\n[8/9] Checking for Collision-Prone Keys...")
collision_warnings = validate_reserved_keys(content)
if collision_warnings:
warnings.extend(collision_warnings)
print(f" ⚠️ Found {len(collision_warnings)} potential key collisions")
else:
print(" ✅ Key safety check passed")
# ========== 9. Active Agent Compliance ==========
print("\n[9/9] Checking Active Agent Compliance...")
active_errors = validate_active_agent_compliance(content)
if active_errors:
all_errors.extend(active_errors)
print(f" ❌ Found {len(active_errors)} items missing ZABBIX_ACTIVE type")
else:
print(" ✅ Active Agent compliance check passed")
# ========== Summary ========== # ========== Summary ==========
print("\n" + "=" * 60) print("\n" + "=" * 60)