SCENARIO

OCSInventory è uno dei tanti software open source che si occupano di gestire, in maniera più o meno automatica, tutti gli asset IT presenti all'interno di una rete: tramite un agent (disponibile per praticamente tutte le piattaforme) è possibile automatizzare moltissime attività (NOTA: devo ancora provarle!). Nel mio caso, la funzione interessata era al momento la parte INVENTORY dato che per tutte le altre funzioni le GPO sono da preferirsi!

Trattandosi di un applicativo "stand alone", OCSInventory dispone di un suo database di utenti per l'accesso alle varie funzioni dell'applicativo.Gli sviluppatori sono però stati particolarmente lungimiranti ed hanno previsto diversi sistemi non solo per la gestione del database gli utenti ma anche per i sistemi di autenticazione e gestione delle autorizzazioni (in pratica hanno correttamente implementato la filosofia AAA - Accounting, Authentication e Authorization). Suggerisco di consultare il sito internet del prodotto per maggiori informazioni - notare che questo prodotto lo utilizzo da circa una settimana per cui la mia conoscenza è al momento decisamente parziale, complice il fatto che non esiste un manuale e quindi non è applicabile l'amata filosofia RTFM. OCSinventory consente di usare diversi metodi:
  • Accounting: database di utenti locali, LDAP oppure CAS
  • Authentication: tramite form, "delegata" al webserver oppure LDAP
  • Authorization: tramite DB locale oppure LDAP
Va da se che ovviamente le combinazioni "usabili" sono solo alcune tra tutte quelle possibili.
Per essere pignoli è necessario dire che OCSInventory riesce sì a sfruttare sistemi esterni, ma questa informazione viene anche aggiunta al suo DB locale: questo significa che, guardando la lista degli users di OCS, si troveranno tutti gli utenti che, fino a quel momento, si sono loggati correttamente almeno una volta sul sistema.



IL SINGLE SIGN ON
Implementare una soluzione SSO significa sostanzialmente che la richiesta delle credenziali all'utente deve essere fatta una volta sola ovvero alla login: chiaramente, per poter funzionare, questo sistema richiede che i vari programmi vengano scritti con la possibilità di interfacciarsi alle apposite librerie di autenticazione messe a disposizione dal S.O. per verificare l'identità dell'utente e concedergli o meno l'accesso ad una risorsa.

Nel caso in oggetto la situazione risulta la seguente:
  • Accounting: Active Directory (ovvero le mie credenziali sono definite nei domain controller)
  • Authentication: NTLM
  • Authorization: Active Directory/LDAP

Due parole sulla scelta dell'NTLM come protocollo di autenticazione: è ormai patrimonio di tutti che sia Internet Explorer che Chrome (anche Firefox se opportunamente configurato) sono in grado di passare automaticamente le credenziali dell'utente loggato alle seguenti condizioni:

  • Il web server richiede l'autenticazione PRIMA di fornire la pagina richiesta (situazione ben diversa dalle classiche pagine con i campi utente e password). In altre parole, in questo caso il WEB server richiede le credenziali ogni volta che il client si collega mentre nel caso della form la gestione di autenticazione avviene a livello di "sessione" applicativa.
  • Il sito appartiene alla zona "Intranet"  ovvero la URL è del tipo http://nomeserver/path
  • Il protocollo di autenticazione è NTLM che (in termini semplicistici) consente di non far transitare la password "in chiaro" sulla rete

Sulla base di queste considerazioni, per consentire un accesso "seamless" a OCSInventory è necessario intervenire su più punti:

  • Configurare Apache in modo da supportare l'autenticazione NTLM interfacciandosi con Active Directory
  • Forzare l'autenticazione per la risorsa /ocsreports che altro non è che la virtual directory dove si installa l'interfaccia web di OCSNG
  • Configurare OCSInventory in modo tale che la parte authorization sia di tipo LDAP (normalmente viene testata l'appartenenza dell'utente ad un gruppo).
  • Configurare OCSInventory in modo tale che sia la parte accounting che la parte authentication siano delegate ad Apache


CONFIGURARE LDAP VERSO ACTIVE DIRECTORY SU OCSINVENTORY

Come si nota dallo specchietto iniziale, OCS Inventory usa LDAP sia per la parte di AUTENTICAZIONE che per la parte di AUTORIZZAZIONE:
  • Per l'AUTENTICAZIONE OCSInventory tenta di fare un bind al server LDAP usando come username e password quelle digitate dall'utente  (questa parte non ci interessa in quanto, come già detto, l'autenticazione verrà delegata ad apache con modulo NTLM)
  • Per l'AUTORIZZAZIONE OCSInventory si collega a LDAP, cerca l'utente nel directory e verifica il valore di uno degli attributi dell'utente stesso.

Per poter attivare questa configurazione è necessario:

  • il supporto LDAP per PHP (verificabile tramite un phpinfo)
  • avere un utente ed una password validi per collegarsi ad Active Directory. Questo utente serve solamente per fare delle query quindi non deve avere nessun privilegio speciale (NON METTETECI ADMINISTRATOR come fa il 80% degli wannabe sysadmins che usano sempre administrator/root quando viene richiesto un utente!!angry smile)


Per fare prima riporto una screenshot della mia configurazione (naturalmente i dati sono di esempio):

ocs ldap1

Una breve e veloce spiegazione di alcuni parametri:
  • CONEX_ROOT_DN: è il distinguished name dell'utente usato per fare le query LDAP. Notare che OCS vuole proprio il DN dell'oggetto e non semplicemente lo username. Inutile dire che conviene creare un utente ad hoc, con requisiti minimi, per queste operazioni
  • CONEX_ROOT_PWD: è la password dell'utente sopra specificato
  • CONEX_DN_BASE_LDAP: è il punto da dove si inizierà a cercare l'utente che si è appena validato (poco importa se tramite "form" oppure in maniera integrata dal webserver)
  • CONEX_LOGIN_FIELD: è l'attributo del directory che verrà confrontato con lo username derivante dall'operazione di autenticazione. Una volta che l'utente è stato validato, OCS "cerca" nel directory un oggetto il cui CONEX_LOGIN_FIELD sia uguale allo username. Visto che siamo nel mondo AD, specificando samaccountname troveremo esattamente l'utente che si è validato.
  • CONEX_LDAP_CHECK_FIELD1_NAME e CONEX_LDAP_CHECK_FIELD1_VALUE: una volta individuato l'utente in Active directory, OCS prenderà l'attributo indicato in CONEX_LDAP_CHECK_FIELD1_NAME e verificherà se il suo valore è uguale a quanto specificato su CONEX_LDAP_CHECK_FIELD1_VALUE: in caso affermativo, all'utente verranno dati i privilegi specificati nell'ultima voce indicata ovvero CONEX_LDAP_CHECK_FIELD1_ROLE.

Nel caso si voglia quindi usare l'appartenenza ad un gruppo come criterio per l'assegnazione dei ruoli dentro OCS Inventory, si dovrà specificare memberof nella voce CONEX_LDAP_CHECK_FIELD1_NAME ed il CN del gruppo in CONEX_LDAP_CHECK_FIELD1_VALUE .

Piccola nota tecnica: memberOf è un multi vaule attribute e quindi restituisce un array di valori. Le ultime versioni di OCS Inventory (quanto sopra è stato fatto con la 2.0.4)  riescono a gestire la particolarità DEL SOLO CAMPO MEMBEROF senza alcun problema (traduzione: non provate con altri multi value attribute perchè non funzionerà - nel codice c'è proprio scritto "se il CONEX_LDAP_CHECK_FIELD1_NAME è uguale a memberof, allora tratta il valore di ritorno da LDAP come un array)

ATTENZIONE! il confronto è case sensitive: quindi fate attenzione a scrivere il CONEX_LDAP_CHECK_FIELD1_VALUE con le appropriate maiuscole e minuscole

CONFIGURAZIONE APACHE
Per attivare l'SSO è necessario attivare, lato Apache, la richiesta di credenziali prima di accedere alla risorsa /ocsreports : normalmente OCSNG mette la configurazione relativa alla sua directory nel file /etc/httpd/conf.d/ocsinventory-reports.conf .
 
apache ocsng

In rosso sono riportate le modifiche da fare: si tratta in pratica di richiedere l'autenticazione per accesso alla risorsa (require valid-user e commento dei predefiniti Order deny, allow e allow from all presenti in fondo), oltre ad una piccola modifica definita attraverso il RewriteEngine; prima di prendere spavento (personalmente ho sempre un po' di difficoltà con i rewrite di Apache), è bene subito dire che il motore di Rewrite, in questo caso, è usato impropriamente per poter definire il valore della variabile HTTP_AUTH_USER.

Vediamo di spiegare la situazione: normalmente, quando l'utente è autenticato dal webserver, lo username viene normalmente messo nella variabile d'ambiente REMOTE_USER (per essere precisi, posso essere sicuro per Apache e per IIS, non so altri web server). Se si guarda il codice di OCSInventory (ed in particolare il file backend/AUTH/auth.php) si scopre invece che quest'ultimo usa la variabile HTTP_AUTH_USER per prendere lo username (nota: di questa variabile ho trovato poche indicazioni anche sul manuale di PHP).

Modificare il file sembrerebbe essere la soluzione più semplice e veloce, ma chi può garantirmi che, altrove, non scappi nuovamente fuori questa variabile "strana"? Ho preferito quindi ricreare la variabile di ambiente dandogli lo stesso valore di REMOTE_USER. La rewrite rule in pratica si applica a tutti gli indirizzi (vedi il .*), non effettua nessuna riscrittura (il -) ed esegue il codice tra parentesi quadra che non fa altro che definire una variabile di ambiente (E=HTTP_AUTH_USER) e assegnargli lo stesso valore della variabile %{REMOTE_USER}.

Prima che qualcuno possa dirlo, non sono riuscito ad usare l'apposita funzione di Apache SetEnv in quanto sembra che quest'ultima non supporti, come valore, un'atra variabile di ambiente.


CONFIGURARE LA DELEGA DELL'AUTENTICAZIONE/AUTORIZZAZIONE 
La documentazione di OCS Inventory spiega abbastanza bene come configurare l'autenticazione tramite un sistema "esterno" (nel nostro caso APACHE tramite NTLM). Il tutto si riduce alla configurazione di alcuni valori all'interno del file PATH_OCSREPORTS/backend/AUTH/AUTH.php.  Riporto la parte saliente della configurazione:

//If you want a html form for the connexion 
//put $affich_method='HTML' 
$affich_method='HTML'; 

//If you use an SSO connexion 
//use this configuration 
//$affich_method='SSO'; 
//$list_methode=array(0=>"always_ok.php"); 

// Author: FranciX // http://forums.ocsinventory-ng.org/viewtopic.php?pid=30974 
//If you use an CAS connexion 
//use this configuration 
//$affich_method='CAS'; 
//$list_methode=array(0=>"always_ok.php"); 

//list of the identification method 
//3 pages by default: ldap.php => LDAP Connexion 
// local.php => Local connexion on ocs base 
// always_ok.php => connexion always ok
 $list_methode=array(0=>"local.php");
// $list_methode=array(0=>"ldap.php");

Come si legge nel file, in base al tipo di autenticazione scelta, è necessario impostare due variabili. La configurazione da fare sembra ben spiegata nei commenti ma purtroppo è errata (o meglio, non si applica al nostro caso). Se si segue alla lettera questa configurazione si otterrà questo messaggio:

ocs noauthorized

In altre parole, l'autenticazione ha funzionato (infatti non ci sono richieste di password) ma la fase di autorizzazione no. Anche correttamente impostando la parte di autenticazione su LDAP (vedi oltre come), il sistema non riuscirà a darci gli opportuni diritti in quanto la funzione LDAP della parte authorization è legata a doppio filo all'omologa della parte authentication che la documentazione dice di saltare.


Quindi, per poter ottenere il risultato voluto è innanzitutto necessario fare queste configurazioni nel file PATH_OCSREPORTS/backend/AUTH/auth.php

$affich_method = 'SSO';
$list_methode =array(0=>"ldap.php");

E quest'altra nel file  PATH_OCSREPORTS/backend/identity/identity.php: 

$list_methode=array(0=>"local.php");
$list_methode=array(0=>"ldap.php");

Il lavoro però non è finito: se si fa mente locale a quanto letto precedentemente, si ricorderà che il metodo di AUTENTICAZIONE LDAP prevede che OCS faccia un bind al server LDAP usando username e password passate dall'utente: peccato però che con l'autenticazione SSO l'applicativo avrà a disposizione solo lo username e non la password gestita appunto da Apache, e di conseguenza il BIND LDAP fallirà invalidando l'autenticazione.

A dimostrazione di questo fatto basta scorrere più in basso nel file PATH_OCSREPORTS/backend/AUTH/auth.php fino ad arrivare a questa sezione:

if ($affich_method == 'HTML' and isset($protectedPost['Valid_CNX']) and trim($p$
        $login=$protectedPost['LOGIN'];
        $mdp=$protectedPost['PASSWD'];
 }elseif ($affich_method == 'CAS'){
        require_once('methode/cas.php');
 }elseif ($affich_method != 'HTML' and isset($_SERVER['PHP_AUTH_USER'])){
        $login=$_SERVER['PHP_AUTH_USER'];
        $mdp=$_SERVER['PHP_AUTH_PW'];
 }elseif($affich_method == 'SSO' and isset($_SERVER['HTTP_AUTH_USER'])){
        $login=$_SERVER['HTTP_AUTH_USER'];
        $mdp='NO_PASSWD';
 }

La parte che ci interessa è quella commentata in rosso dove si vede bene che, in caso di SSO, solo la variabile $login viene popolata mentre la variabile $mdp che, guardando le altre sezioni, dovrebbe contenere la password dell'utente, viene impostata ad al valore fisso NO_PASSWD.

Per risolvere il problema basta mettere le mani anche nel file PATH_OCSREPORTS/backend/AUTH/methode/ldap.php in questa sezione:
 

function verif_pw_ldap($login, $pw) { 
     $info = search_on_loginnt($login); 
     if ($info["nbResultats"]!=1) 
         return ("BAD LOGIN OR PASSWORD");   // login does't exist 

     return (ldap_test_pw($info[0]["dn"], $pw) ? "OK" : "BAD LOGIN OR PASSWORD")$
}

Senza entrare nei dettagli, è sufficiente dire che la funzione verif_pw_ldap viene chiamata da ultimo nella validazione LDAP e, nella sua ultima riga (evidenziata in rosso) restituisce OK oppure BAD LOGIN OR PASSWORD se non riesce a fare il bind a LDAP con l'utente e la password passate.. Come già detto, però, dato che la password non c'è, questa funzione fallirà sempre!


La soluzione è quella di aggiungere una piccola condizione alla funzione:

function verif_pw_ldap($login, $pw) { 
     $info = search_on_loginnt($login); 
     if ($info["nbResultats"]!=1) 
         return ("BAD LOGIN OR PASSWORD");   // login does't exist 

     // Hack for SSO
     if ($pw == 'NO_PASSWD') 
         return ("OK");   // Already verified by Apache

     return (ldap_test_pw($info[0]["dn"], $pw) ? "OK" : "BAD LOGIN OR PASSWORD")$
}

In pratica, la parte aggiunta dice che il risultato della validazione LDAP è "OK" se il campo password è impostato a NO_PASSWD (che è il valore impostato da SSO).

NOTE FINALI
Mentre fate le varie prove, è consigliabile, tra una modifica e l'altra, il riavvio di Apache e la cancellazione dei files della sessione PHP (nella mia CentOS stanno in /var/lib/php/session) dato che OCSInventory usa pesantemente le variabili di sessione.