Files
2026-05-22 13:55:01 +02:00

12 KiB


🧠 MEMORY.md

# 🧠 MEMORY.md - Knowledge Base Progetto NetConfig Plugin

> **Scopo**: Memoria persistente delle decisioni, contesto e apprendimenti per il team di sviluppo  
> **Progetto**: GLPI 11 Plugin - Network Configuration Backup & Versioning  
> **Stato**: ✅ Specifica Completa | 🚧 Implementazione Pronta | 🧪 Test in Corso

---

## 📋 Riepilogo Richieste Utente

### Richiesta Originale (Tradotta e Strutturata)

"Vorrei implementare un plugin per GLPI 11 secondo le linee guida degli sviluppatori GLPI per:

  1. Salvare configurazioni di apparati di rete (es. switch) nel database MySQL
  2. Tenere traccia delle variazioni della configurazione (versioning)
  3. Implementare massive actions (azioni di massa)
  4. Gestire i profili come già integrato in GLPI (ACL native)
  5. Permettere l'esportazione in file di testo del contenuto salvato (solo per utenti con diritti)
  6. Lanciare il comando per recuperare la configurazione dall'apparato di rete
  7. Valutare l'utilizzo di un agent GLPI remoto per eseguire il recupero configurazione
  8. Fornire una proposta architetturale completa"

### Vincoli Espliciti
- ✅ GLPI 11.0.6+ (verificato con `php bin/console glpi:version`)
- ✅ PHP 8.2+ con strict_types e namespace PSR-4
- ✅ MySQL/MariaDB con charset utf8mb4
- ✅ Integrazione con sistema profili/diritti nativo GLPI
- ✅ Massive actions compatibili con UI GLPI
- ✅ Cifratura configurazioni sensibili
- ✅ Agent remoto opzionale ma preferito per scalabilità

---

## 🗂️ Decisioni Architetturali Confermate

### ✅ Scelte Definitive
| Area | Decisione | Motivazione |
|------|-----------|-------------|
| **Struttura Plugin** | PSR-4 autoload, `src/` per classi, `ajax/` per endpoint | Conformità linee guida GLPI 11, manutenibilità |
| **Database** | Tabella dedicata `glpi_plugin_netconfig_configs` con MEDIUMTEXT | Isolamento dati, performance query, supporto config grandi |
| **Versioning** | SHA256 hash + confronto all'arrivo nuovo backup | Rilevamento modifiche efficiente, storage ottimizzato |
| **Cifratura** | `Glpi\Toolbox\Encryption` (AES-256-GCM) | Integrazione nativa, chiave gestita da GLPI, compliance |
| **Agent Remoto** | Python + netmiko + YAML config + systemd timer | Supporto multi-vendor maturo, gestione timeout/retry robusta |
| **Diff Visual** | `symfony/diff` con output HTML sanitizzato per Twig | Libreria mantenuta, output sicuro, integrazione semplice |
| **Export** | Stream diretto con redaction regex + controllo diritti | Performance, sicurezza, compliance policy aziendali |
| **API Endpoint** | POST JSON con token + validazione hash lato server | Stateless, compatibile con agent distribuiti, auditabile |

### ❌ Alternative Scartate (con Motivazione)
| Alternativa | Motivazione Scarto |
|-------------|-------------------|
| SSH diretto da PHP (phpseclib) | Timeout frequenti, gestione connessioni concorrenti complessa, scaling limitato |
| Archiviazione su filesystem | Perdita integrazione ACL GLPI, backup/disaster recovery più complesso, audit difficile |
| Integrazione Oxidized/RANCID | Overhead infrastrutturale aggiuntivo, duplicazione logica versioning, curva apprendimento team |
| Agent in PHP invece di Python | Netmiko/napalm hanno supporto multi-vendor più maturo e community attiva in Python |
| Diff calcolato lato client (JS) | Configurazioni grandi (>1MB) causano lag browser, sanitizzazione complessa per XSS |

---

## 🔐 Security & Compliance Decisions

### Credenziali e Segreti
```yaml
# ✅ DO: Usare variabili d'ambiente o vault esterno
NETCONFIG_DEFAULT_USER: "backup_svc"
NETCONFIG_DEFAULT_PASS: "${VAULT_SECRET_NETCONFIG_PASS}"
NETCONFIG_AGENT_TOKEN: "${OPENSSL_GENERATED_32B_HEX}"

# ❌ DON'T: Hardcodare nel codice o YAML
# password: "SuperSecret123"  # MAI fare questo

Cifratura Dati

  • A riposo: config_content cifrato con Glpi\Toolbox\Encryption::encrypt()
  • Chiave: Derivata da crypt_key in GLPI_CONFIG_DIR/config_db.php (gestita da GLPI)
  • Algoritmo: AES-256-GCM con autenticazione (previene tampering)
  • Verifica: Test manuale post-install per confermare che DB contenga blob cifrati

Controllo Accessi

// Pattern confermato per tutti i metodi sensibili
if (!\PluginNetconfig\Config::canView()) {
    http_response_code(403);
    exit('Access denied');
}

Redaction Export

// Regex per mascherare credenziali prima dell'export
$content = preg_replace(
    '/(?<=password\s|secret\s|enable\s|community\s)[^\s\r\n]+/i', 
    '***REDACTED***', 
    $content
);
// Personalizzabile in base alle policy aziendali

🧪 Test Plan Confermato

Test Unitari (PHPUnit - da implementare)

// Esempio: test hash detection
public function testSaveConfig_DetectsNoChanges(): void
{
    $config = new \PluginNetconfig\Config();
    $result = $config->saveConfig([
        'networkdevices_id' => 123,
        'content' => 'identical config content'
    ]);
    // Primo salvataggio: true
    // Secondo salvataggio stesso contenuto: true ma nessun nuovo record
}

Test di Integrazione

  1. Installazione plugin: Verificare creazione tabella e registrazione hook
  2. Diritti profilo: Assegnare READ a utente test, verificare accesso a tab Config History
  3. Agent simulation: Invio JSON via curl, verificare risposta e record in DB
  4. Export flow: Login utente con diritti, click export, verificare download e redaction
  5. Diff visual: Salvare due versioni diverse, verificare rendering HTML differenze

Test di Sicurezza

# 1. Tentativo export senza diritti
curl -b "glpi_session=invalid" http://glpi/plugins/netconfig/ajax/export.php?id=1
# Atteso: HTTP 403

# 2. Invio agent con token errato
curl -X POST ... -d '{"token":"wrong"}'
# Atteso: {"status":"error","message":"Unauthorized"}

# 3. Verifica cifratura DB
mysql -e "SELECT config_content FROM glpi_plugin_netconfig_configs LIMIT 1" | head
# Atteso: blob binario non leggibile, non testo in chiaro

📦 Dipendenze e Versioni Confermate

composer.json (Definitivo)

{
  "name": "glpi-plugin/netconfig",
  "license": "GPL-3.0-or-later",
  "minimum-stability": "stable",
  "prefer-stable": true,
  "require": {
    "php": ">=8.2",
    "symfony/diff": "^6.4",
    "guzzlehttp/guzzle": "^7.8"
  },
  "autoload": {
    "psr-4": {
      "PluginNetconfig\\": "src/",
      "PluginNetconfig\\Api\\": "src/Api/"
    }
  }
}

Requisiti Python Agent

# requirements-agent.txt
netmiko>=4.3.0
requests>=2.31.0
pyyaml>=6.0.1
cryptography>=41.0.0  # Per eventuale cifratura lato agent (opzionale)

Estensioni PHP Richieste

php -m | grep -E "curl|json|mbstring|openssl|zip|pdo_mysql"
# Tutte devono essere presenti per GLPI 11 + plugin

🔄 Flussi Operativi Documentati

Flusso 1: Backup Schedulato (Agent Remoto)

sequenceDiagram
    participant Cron as Systemd Timer/Cron
    participant Agent as GLPI Agent (Python)
    participant Device as Network Device
    participant GLPI as GLPI Server + Plugin
    
    Cron->>Agent: Trigger ogni 6h
    Agent->>Device: SSH via netmiko (show running-config)
    Device-->>Agent: Restituisce configurazione
    Agent->>Agent: Calcola SHA256, confronta con ultimo locale (opzionale)
    Agent->>GLPI: POST /ajax/agent_receive.php con JSON
    GLPI->>GLPI: Valida token, decifra chiave, confronta hash DB
    alt Hash diverso
        GLPI->>GLPI: Cifra contenuto, salva nuovo record, registra versione
        GLPI-->>Agent: {"status":"ok","message":"Saved"}
    else Hash identico
        GLPI-->>Agent: {"status":"ok","message":"No changes"}
    end
    Agent->>Cron: Log risultato, exit

Flusso 2: Export Configurazione (Utente GLPI)

sequenceDiagram
    participant User as Utente GLPI
    participant UI as Interfaccia GLPI
    participant Plugin as Plugin NetConfig
    participant DB as Database MySQL
    
    User->>UI: Clicca "Export" su configurazione
    UI->>Plugin: GET /ajax/export.php?id=XXX
    Plugin->>Plugin: Verifica Session::haveRight(Config::READ)
    alt Diritti insufficienti
        Plugin-->>UI: HTTP 403 + messaggio errore
    else Diritti OK
        Plugin->>DB: SELECT config_content, is_encrypted WHERE id=XXX
        DB-->>Plugin: Restituisce record
        Plugin->>Plugin: Decifra se is_encrypted=1
        Plugin->>Plugin: Applica regex redaction su password/secret
        Plugin-->>User: HTTP 200 + file .txt in download
    end

Flusso 3: Visualizzazione Diff (UI GLPI)

sequenceDiagram
    participant User as Utente GLPI
    participant Twig as Template Engine
    participant Plugin as Plugin NetConfig
    participant DiffLib as symfony/diff
    
    User->>UI: Apri tab "Config History" su NetworkEquipment
    UI->>Plugin: Richiedi liste configurazioni per device_id
    Plugin->>DB: SELECT * ORDER BY created_at DESC
    DB-->>Plugin: Restituisce array configurazioni
    Plugin->>Plugin: Per l'ultima config, carica precedente e calcola diff
    Plugin->>DiffLib: Differ::diff(old, new) con HtmlOutput
    DiffLib-->>Plugin: Stringa HTML con <ins>/<del>
    Plugin->>Twig: Passa configs + diff_html al template
    Twig->>User: Renderizza tabella con pulsante "View Diff"
    User->>UI: Clicca pulsante diff
    UI->>User: Modal dialog con differenze evidenziate (verde/rosso)

🚨 Lessons Learned & Pitfalls Evitati

Cosa Abbiamo Imparato

  1. Composer in ambienti non-standard: Il warning installed.json not found è innocuo se composer install completa con successo. Non bloccare l'installazione per questo.
  2. Hash vs Timestamp per versioning: Confrontare hash SHA256 è più affidabile dei timestamp (orologi non sincronizzati, config identiche con timestamp diversi).
  3. Cifratura nativa GLPI: Usare Glpi\Toolbox\Encryption evita di gestire chiavi separatamente e garantisce compatibilità con backup/restore GLPI.
  4. Diff on-demand: Calcolare il diff solo per l'ultima configurazione (non per tutto lo storico) migliora le performance con config grandi.
  5. Agent token rotation: Documentare la procedura di rotazione token nel README operativo, anche se non implementata nel codice.

⚠️ Pitfalls Evitati

Problema Potenziale Come Lo Abbiamo Evitato
XSS nel diff HTML Output di symfony/diff sanitizzato con `
SQL injection in massive actions Uso di prepared statements in getLastConfigByDevice()
Credential leak nei log Logging configurato per escludere payload config, solo metadata
Timeout SSH su dispositivi lenti Retry logic con backoff + global_delay_factor configurabile per device
Memory exhaustion con config grandi Stream export invece di caricamento intero in memoria, uso di MEDIUMTEXT

📞 Contatti e Responsabilità

Ruolo Responsabilità Contatto (Esempio)
Plugin Maintainer Release, compatibilità GLPI, security patch team-dev@azienda.it
Agent Operator Deploy script Python, gestione dispositivi YAML, monitoraggio netops@azienda.it
Security Officer Approvazione policy redaction, rotazione token, audit cifratura security@azienda.it
GLPI Admin Installazione plugin, gestione profili, backup database glpi-admin@azienda.it

🗓️ Cronologia Decisioni

Data Decisione Autore Riferimento
2026-05-22 Architettura plugin confermata (PSR-4, encryption nativa, agent Python) AI Assistant + Utente Richiesta iniziale
2026-05-22 Scelta symfony/diff ^6.4 per visualizzazione differenze AI Assistant Valutazione librerie
2026-05-22 Token-based auth per endpoint agent invece di session GLPI AI Assistant Requisito agent remoto
2026-05-22 Redaction automatica password in export tramite regex AI Assistant Compliance sicurezza
2026-05-22 Supporto massive actions native invece di UI custom AI Assistant Integrazione UX GLPI


📌 Nota Operativa: Questo documento va aggiornato ad ogni modifica architetturale significativa.
Ultima revisione: 2026-05-22 | Prossima review pianificata: 2026-08-22


---