Files
netconfig/for_glpi_agent/-usr-share-glpi_agent-plugins_/netconfig_backup.py
T

200 lines
6.7 KiB
Python
Raw Normal View History

2026-05-22 13:55:01 +02:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
GLPI Agent Task: netconfig_backup
Recupera configurazione da apparati di rete e invia a GLPI plugin netconfig
Requisiti: netmiko, requests, pyyaml
Install: pip3 install netmiko requests pyyaml
"""
import os
import sys
import json
import hashlib
import logging
import requests
from datetime import datetime
from typing import Optional, Dict, List
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
import yaml
# Configurazione logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/glpi_agent_netconfig.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger('netconfig_backup')
# Configurazione da file YAML esterno (sicuro, non hardcodato)
CONFIG_FILE = os.getenv('NETCONFIG_CONFIG', '/etc/glpi-agent/netconfig_devices.yaml')
GLPI_URL = os.getenv('GLPI_URL', 'http://localhost/glpi')
GLPI_TOKEN = os.getenv('GLPI_APP_TOKEN', '')
AGENT_TOKEN = os.getenv('NETCONFIG_AGENT_TOKEN', 'change_me_in_production')
TIMEOUT = int(os.getenv('NETCONFIG_TIMEOUT', '30'))
RETRIES = int(os.getenv('NETCONFIG_RETRIES', '2'))
def load_devices_config(path: str) -> List[Dict]:
"""Carica configurazione dispositivi da YAML"""
if not os.path.exists(path):
logger.error(f"Config file not found: {path}")
return []
try:
with open(path, 'r') as f:
data = yaml.safe_load(f)
return data.get('devices', [])
except Exception as e:
logger.error(f"Error loading config: {e}")
return []
def get_device_connection_params(device: Dict) -> Dict:
"""Prepara parametri di connessione per netmiko"""
device_type_map = {
'cisco_ios': 'cisco_ios',
'cisco_nxos': 'cisco_nxos',
'hp_comware': 'hp_comware',
'juniper_junos': 'juniper_junos',
'arista_eos': 'arista_eos',
'paloalto_panos': 'paloalto_panos',
}
return {
'device_type': device_type_map.get(device['platform'], 'cisco_ios'),
'host': device['ip'],
'username': device.get('username', os.getenv('NETCONFIG_DEFAULT_USER')),
'password': device.get('password', os.getenv('NETCONFIG_DEFAULT_PASS')),
'secret': device.get('enable_password', os.getenv('NETCONFIG_ENABLE_PASS')),
'port': device.get('port', 22),
'timeout': TIMEOUT,
'session_log': None,
'global_delay_factor': device.get('delay_factor', 1),
}
def fetch_config(device: Dict) -> Optional[str]:
"""Recupera configurazione via SSH con netmiko"""
params = get_device_connection_params(device)
for attempt in range(RETRIES + 1):
try:
logger.info(f"Connecting to {device['name']} ({params['host']}) [attempt {attempt+1}]")
connection = ConnectHandler(**params)
# Abilita modalità enable se necessaria
if params.get('secret'):
connection.enable()
# Comando di show config (personalizzabile per piattaforma)
command = device.get('command', 'show running-config')
config = connection.send_command(command, expect_string=r'#|\$', max_loops=150)
connection.disconnect()
# Pulizia output da caratteri di controllo
config = config.strip()
config = '\n'.join(line.rstrip() for line in config.splitlines())
logger.info(f"Config fetched successfully for {device['name']}")
return config
except NetmikoAuthenticationException as e:
logger.error(f"Auth failed for {device['name']}: {e}")
break # Non ritentare se auth fallisce
except NetmikoTimeoutException as e:
logger.warning(f"Timeout for {device['name']}: {e}")
except Exception as e:
logger.error(f"Unexpected error for {device['name']}: {e}")
return None
def send_to_glpi(device_id: int, config: str) -> bool:
"""Invia configurazione a GLPI plugin endpoint"""
url = f"{GLPI_URL}/plugins/netconfig/ajax/agent_receive.php"
payload = {
'device_id': device_id,
'config': config,
'token': AGENT_TOKEN,
'timestamp': datetime.utcnow().isoformat(),
'agent_version': '1.0.0'
}
headers = {
'Content-Type': 'application/json',
'App-Token': GLPI_TOKEN, # Opzionale: autenticazione API GLPI
}
try:
response = requests.post(
url,
json=payload,
headers=headers,
timeout=30,
verify=os.getenv('SSL_VERIFY', 'true').lower() == 'true'
)
if response.status_code == 200:
result = response.json()
logger.info(f"GLPI response: {result.get('message', 'OK')}")
return result.get('status') == 'ok'
else:
logger.error(f"GLPI HTTP {response.status_code}: {response.text}")
return False
except requests.RequestException as e:
logger.error(f"Request to GLPI failed: {e}")
return False
def calculate_hash(config: str) -> str:
"""Calcola SHA256 della configurazione"""
return hashlib.sha256(config.encode('utf-8')).hexdigest()
def run_task():
"""Entry point della task"""
logger.info("=== NetConfig Backup Task Started ===")
devices = load_devices_config(CONFIG_FILE)
if not devices:
logger.warning("No devices configured, exiting")
return
success_count = 0
for device in devices:
try:
device_name = device.get('name', 'unknown')
device_id = device.get('glpi_id') # ID in glpi_networkdevices
if not device_id:
logger.warning(f"Skipping {device_name}: missing glpi_id")
continue
config = fetch_config(device)
if not config:
logger.warning(f"Failed to fetch config for {device_name}")
continue
# Invia a GLPI solo se diverso dall'ultimo (controllo lato server, ma ottimizzazione client)
if send_to_glpi(device_id, config):
success_count += 1
logger.info(f"{device_name} - Config saved")
else:
logger.error(f"{device_name} - Failed to save to GLPI")
except Exception as e:
logger.exception(f"Unhandled error for device {device.get('name')}")
logger.info(f"=== Task Completed: {success_count}/{len(devices)} devices processed ===")
if __name__ == '__main__':
run_task()