SCENARIO

GLPI (www.glpi-project.org) è uno dei tanti prodotti di ausilio per le attività di un dipartimento IT: sebbene la sua principale funzione sia la gestione (dal punto di vista "burocratico") dei vari asset (ben integrandosi con OCS Inventory), risulta particolarmente efficace anche nella parte ticketing, disponendo di un sistema strutturato sulla base del paradigma ITIL.

Purtroppo, l'unica nota negativa di questo prodotto è la mancanza di un vero e proprio manuale: gli autori infatti hanno preferito la strada del WIKI e dei FORUM anzichè realizzare un bel manuale di riferimento. E' purtroppo vero che al giorno d'oggi la filosofia RTFM è in declino, ma è altrettanto vero che la diffusione di wiki e forum (e forse anche di questo mio sito) ha anche contribuito alla comparsa di sedicenti esperti che si sentono tali solo per aver letto un po' di articoli su Wikipedia o per aver frequentato un forum.

Dopo aver implementato la parte di ASSET MANAGEMENT, è stato deciso che anche la gestione dei ticket era senza dubbio meritevole: mi sono quindi ritrovato con l'esigenza di gestire l'accesso al sistema non solo per gli amministratori ma anche per gli utenti finali. Sebbene si potesse scegliere una soluzione di accesso "anonimo" (dopotutto è un sito Intranet), ho preferito implementare l'autenticazione degli utenti in modo tale da avere già "pre compilati" una serie di campi nel ticket di richiesta (es. l'indirizzo mail, il PC assegnato all'utente e ecosì via).

Fin qui niente di strano, anzi i seguenti link spiegano per filo e per segno come implementare questa funzione:
  1. Importare gli utenti da AD a GLPIhttp://www.glpi-project.org/wiki/doku.php?id=en:ldap): in parole semplici, è necessario configurare GLPI per fare delle query LDAP ai domain controller ed importarsi automaticamente gli utenti e una parte dei loro attributi (es. indirzzo email - comodo se si ha Exchange -  e numero di telefono)
  2. Autenticazione NTLMhttp://www.glpi-project.org/wiki/doku.php?id=en:authautoad ): configurare Apache con appositi moduli per l'autenticazione NTLM (ce ne sono diversi, non è necessario usare quello proposto), richiedere l'autenticazione per l'accesso all'URl di pubblicazione GLPI e configurare quest'ultimo per prendere la variabile $_SERVER['REMOTE_USER']  che conterrà appunto l'utente precedentemente autenticato da Apache.

Nel mio caso, però, il problema era legato alla presenza di più domini/foreste AD e di conseguenta la necessità di autenticare e persone provenienti da domini diversi. Ed è per questo che mi sono inventato una soluzione un po' articolata...

IL PROBLEMA
Come già detto, gli utenti di questa rete si trovano distributi su più domini AD e la macchina server (Linux CentOS) è joinata ad uno di questi domini che funziona da "master": al momento le relazioni di trust non sono complete (p.s. e non volete sapere il perchè whatchutalkingabout smile), con il risultato che gli utenti dei domini "slave" non sono conosciuti alle risorse del dominio master. Questo in pratica determina l'impossibilità di utilizzare le componenti Apache di winbind (già presente vista la "join" con il dominio).

Fortunatamente Apache mette a disposizione almeno altri due sistemi per gestire autenticazione NTLM:
  • Il primo (anche usato da GLPI nei propri esempi) è il mod_ntlm, modulo aggiuntivo da compilare/caricare su Apache
  • Il secondo è il modulo PERL Authen::NTLM

Il vantaggio di entrambi i moduli (personalmente ho scelto il secondo perchè nella distribuzione in uso non avevo il pacchetto già pronto) è quello di non richiedere praticamente alcuna configurazione lato server (ovvero, join della macchina ad AD) in quanto lavorano semplicemente indicando loro quale dominio/Domain Controller usare per l'autenticazione.

Il comportamento di entrambi i moduli è abbastanza simile:

  • Su Apache si specifica la URL da proteggere (sia essa una directory sul file system oppure una location virtuale)
  • Si abilita e si impostano i vari parametri del modulo NTLM (ovvero nome del dominio e domain controller - si rimanda alla documentazione dello specifico modulo per ulteriori informazioni).
  • Si forza l'autenticazione con la direttiva Apache Require valid-user (oppure group, dipende dal tipo di modulo e dalle proprie necessità)

Proprio in questa semplicità di configurazione si annida il mio problema, dato che con nessuno dei due moduli è possibile specificare più domini verso i quali fare l'autenticazione NTLM. Quindi, attivando l'autenticazione dell'URL di GLPI sul DOMINIO1, gli utenti del DOMINIO2 non sarebbero mai stati riconosciuti.

LA SOLUZIONE
Lo schema sottostante indica la soluzione da me adottata
glpi sso
Vediamo il funzionamento nel dettaglio:

 1. L'utente richiede la URL http://server/glpi/index.php?parametri (magari perchè è quella indicata nella mail di gestione del ticket) oppure la generica http://server/glpi per accedere alla gestione dei ticket 
 2.
Su Apache esiste una "rewrite rule" (in realtà è una "match rule" ma il concetto è praticamente simile)  che si applica a tutti gli url che contengono la voce /glpi/ e li rimanda verso la url /glpi_auth/index.php portandosi dietro eventuali parametri. La riga da inserire nella configurazione è:
AliasMatch /glpi/(.*) /var/www/glpi_auth/index.php 
 3.

Il file index.php presente nella directory /glpi_auth/ simula una richiesta di autenticazione NTLM. Simula significa che, una volta che il client ha passato al server lo username, il dominio e la password, riceve un redirect verso un URL legato al dominio di appartenenza. Il codice di questa pagina è il seguente (n.b. la parte di NTLM l'ho scopiazzata dalla rete):

// This a copy taken 2008-08-21 from http://siphon9.net/loune/f/ntlm.php.txt to make  
// sure the code is not lost.   

// NTLM specs http://davenport.sourceforge.net/ntlm.html 

$headers = apache_request_headers(); 

if (!isset($headers['Authorization'])){    
  header('HTTP/1.1 401 Unauthorized');    
  header('WWW-Authenticate: NTLM');    
  exit;  
} 

$auth = $headers['Authorization']; 

if (substr($auth,0,5) == 'NTLM ') {    
  $msg = base64_decode(substr($auth, 5));    
  
  if (substr($msg, 0, 8) != "NTLMSSP\x00")       
     die('error header not recognised'); 

  if ($msg[8] == "\x01") {      
     $msg2 = "NTLMSSP\x00\x02"."\x00\x00\x00\x00".     // target name len/alloc
             "\x00\x00\x00\x00".                       // target name offset
             "\x01\x02\x81\x01".                       // flags
             "\x00\x00\x00\x00\x00\x00\x00\x00".       // challenge
             "\x00\x00\x00\x00\x00\x00\x00\x00".       // context
             "\x00\x00\x00\x00\x30\x00\x00\x00";       // target info len/alloc/offset 

     header('HTTP/1.1 401 Unauthorized');      
     header('WWW-Authenticate: NTLM '.trim(base64_encode($msg2)));      
     exit;    
  } else if ($msg[8] == "\x03") {      
     $user = get_msg_str($msg, 36);      
     $domain = get_msg_str($msg, 28);      
     $workstation = get_msg_str($msg, 44); 
           
     $targetRedir = "http://" . $_SERVER['HTTP_HOST'];      
     $targetRedir .= str_replace("/glpi/","/glpi_" . strtolower($domain) . 
                     "/",$_SERVER['REQUEST_URI']); 
     header("Location: " . $targetRedir );    
   }  
}
function get_msg_str($msg, $start, $unicode = true) { 
     $len = (ord($msg[$start+1]) * 256) + ord($msg[$start]); 
     $off = (ord($msg[$start+5]) * 256) + ord($msg[$start+4]); 
     if ($unicode) 
          return str_replace("\0", '', substr($msg, $off, $len)); 
     else 
          return substr($msg, $off, $len); 
} 
?> 
 4.  Il client riceve quindi un redirect ad un URL del tipo http://nomeserver/glpi_nomeNETBIOSdominio/[parametri. Ad esempio, se l'utente appartiene al dominio chiamato DOMINIOA e la pagina richiesta era http://server/glpi/index.php?ticket=10, verrà reindirizzato su http://server/glpi_dominioa/index.php?ticket=10
 
5.

La configurazione di Apache prevede anche queste due sezioni ed è ripetuta per ciascuno dei domini gestiti:

glpi apache

Vediamo di spiegare un po' la situazione: la prima riga è appunto un alias, ovvero fa si che tutte le chiamate che iniziano per /glpi_dominioa/ vadano comunque a finire nella directory contentente l'unica installazione del GLPI (da notare che, nella configurazione del GLPI stesso, sarà necessario specificare la directory di installazione come /glpi).
 
Una volta effettuato questo aliasing, entra in gioco la direttiva Location sulla quale è stato per l'appunto attivato il modulo di autenticazione NTLM con i suoi specifici parametri (come già detto, nel mio caso è il modulo Perl Authen::NTLM - se si usa altro modulo, le configurazioni potrebbero essere diverse). La cosa però importante è l'opzione require valid-user che obbliga l'utente ad autenticarsi prima di avere accesso alla URL specificata.

Il browser effettua quindi la seconda autenticazione NTLM (stavolta vera) e il modulo effettua il controllo sui domain controller specificati. Se le credenziali sono corrette, allora l'accesso alla risorsa è consentito, viene valorizzata la variabile $_SERVER['REMOTE_USER'] che sarà poi usata dal GLPI  (ricordo che GLPI ha già importato, tramite il modulo LDAP, gli utenti nel suo sistema.

Sebbene la soluzione possa sembrare macchinosa, si è dimostrata efficace per risolvere il mio specifico problema. Devo però dire che ogni tanto Apache si blocca senza nessuna apparente spiegazione: anche il restart del demone impiega qualche secondo in più e credo che questo sia causato non tanto dal mio trucco quanto dal modulo Perl che si "incarta", soprattutto quanto gli utenti da autenticare appartengono a domini "remoti". Al momento non ho avuto modo di fare un troubleshooting più dettagliato del problema, ma la spiegazione potrebbe anche essere il basso numero di worker process da me configurati su Apache. Vedremo al momento...