WINLOG est maintenant disponible sur GitHub : https://github.com/jbousquie/winlog Documentation : https://github.com/jbousquie/winlog/wiki Le but est de mettre en place un système de surveillance de l'activité de connexion à Active Directory des postes Windows de salles de cours informatiques sans avoir à "éplucher" les tickets Kerberos sur le serveur Windows. Ce système ne doit en outre pas nécessiter de déploiement ou d'installation sur le parc Windows et doit fonctionner simplement avec toutes les versions d'OS (win XP, win 7). L'architecture en place consiste en :
Ces données peuvent ensuite être consultées en temps réel, ou a posteriori, au travers d'une webapp php. Script de connexionLe script de connexion est écrit en VBScript, langage interprété tournant sur toutes les versions de windows. Il est déployé dans la GPO pour être exécuté à la connexion à AD par chaque utilisateur.\\domaine.local\SysVol\domaine.local\Policies\{F292B1A0-F0DD-4704-BEAA-A5F2E7E225CD}\User\Scripts\Logon\logon.vbs Il envoie le code de l'action de connexion "C", le username et le nom de la machine. On envoie aussi un code arbitraire, connu du serveur, pour filtrer d'éventuelles requêtes POST anonymes sur ce dernier. Attention, le code ne doit pas contenir des caractères interférant avec l'encodage URL comme +, &, %, etc. Ce code peut être fixé en dur dans les scripts côté serveur et client une fois pour toute, généré chaque jour des deux côtés, etc. logon.vbs : Dim o, n, data, secopt Script de déconnexionIl s'agit exactement du même script que le script de connexion, mais avec l'envoi d'un code d'action de déconnexion "D".\\domaine.local\SysVol\domaine.local\Policies\{F292B1A0-F0DD-4704-BEAA-A5F2E7E225CD}\User\Scripts\Logoff\logout.vbs logout.vbs : Dim o, n, data, secopt code= valeur_du_code & action=D&username="+ LCase(n.Username) +"&computer="+n.ComputerName Idée : des scripts similaires, codés par exemple en python, pourraient être déployés sur les machines Linux du parc pour execution à l'ouverture/fermeture de session. Serveur webIl s'agit d'un simple serveur Apache + php. On chiffre les flux afin de se protéger d'un éventuel sniff du réseau.Mise en place de SSL : apt-get install openssl cd /etc/apache2 mkdir certs cd certs openssl req -new > winlog.domaine.local.csr openssl rsa -in privkey.pem -out winlog.domaine.local.key openssl x509 -in winlog.domaine.local.csr -out winlog.domaine.local.cert -req -signkey winlog.domaine.local.key -days 1095 a2enmod ssl /etc/ini t.d/apache2 force-reload On créée un virtual host : nano /etc/apache2/sites-available/default.ssl NameVirtualHost 192.168.100.59:443 <VirtualHost 192.168.100.59:443> ServerAdmin webmaster@localhost DocumentRoot /var/www/ SSLEngine on SSLCertificateFile /etc/apache2/certs/winlog.domaine.local.cert SSLCertificateKeyFile /etc/apache2/certs/winlog.domaine.local.key CustomLog /var/log/apache2/ssl-access.log combined ErrorLog /var/log/apache2/ssl-error.log </VirtualHost> On l'active et on le charge : a2ensite default.ssl a2dissite 000-default /etc/ini t.d/apache2 reload script du serveur : Le script sur le serveur va vérifier que la requête est bien un POST et qu'il contient le code partagé entre le client et le serveur avant de stocker un enregistrement de connexion ou de déconnexion. A noter : des filtres supplémentaires (plage d'adresses, ACL, etc) peuvent bien sûr être déployés sur apache ou sur le firewall entre les postes clients et les PC des salles. /var/www/index.php : <?php valeur_du_code "; LA BASE WINLOGLa base de données winlog comprend 4 tables : comptes, connexions, machines et salles.Les tables comptes, machines et salles sont pré-remplies par le script recup_salles.php. Ce dernier interroge Active Directory et récupère les comptes windows dans l'AD, ainsi que le nom des salles et des machines (machines dans des OU). Seule la table connexions est alimentée au fil de l'eau par les connexions/déconnexions des utilisateurs à leur session windows. La table comptes : SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- Structure de la table `comptes` -- CREATE TABLE IF NOT EXISTS `comptes` ( `compte_id` int(11) NOT NULL auto_increment, `username` varchar(25) collate utf8_unicode_ci NOT NULL, `prenom` varchar(25) collate utf8_unicode_ci NOT NULL, `nom` varchar(30) collate utf8_unicode_ci NOT NULL, `groupe` varchar(25) collate utf8_unicode_ci default NULL, PRIMARY KEY (`compte_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=679 ; La table connexions : SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- Structure de la table `connexions` -- CREATE TABLE IF NOT EXISTS `connexions` ( `con_id` bigint(20) NOT NULL auto_increment, `username` varchar(30) collate utf8_unicode_ci NOT NULL, `hote` varchar(15) collate utf8_unicode_ci NOT NULL, `ip` varchar(30) collate utf8_unicode_ci default NULL, `fin_con` timestamp NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, `debut_con` timestamp NULL default '0000-00-00 00:00:00', `close` tinyint(1) NOT NULL default '1', PRIMARY KEY (`con_id`), KEY `user_host` (`username`,`hote`), KEY `close` (`close`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=93667 ; La table machines : SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- Structure de la table `machines` -- CREATE TABLE IF NOT EXISTS `machines` ( `machine_id` varchar(15) collate utf8_unicode_ci NOT NULL, `salle` varchar(10) collate utf8_unicode_ci NOT NULL, `os` varchar(30) collate utf8_unicode_ci NOT NULL, `os_sp` varchar(20) collate utf8_unicode_ci NOT NULL, `os_version` varchar(20) collate utf8_unicode_ci NOT NULL, `ip_fixe` varchar(30) collate utf8_unicode_ci NOT NULL, PRIMARY KEY (`machine_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; La table salles : SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- Structure de la table `salles` -- CREATE TABLE IF NOT EXISTS `salles` ( `salle_id` varchar(10) collate utf8_unicode_ci NOT NULL, `libre_service` tinyint(1) NOT NULL, PRIMARY KEY (`salle_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; script de chargement des tables comptes, salles et machines : recup_salles.php. Il vide les tables au préalable, avant de les remplir à nouveau avec les valeurs récupérées dans l'AD. <?php // Récupération des salles et des machines dans l'AD, chargement dans la base include('winlog_admin.conf'); // Connexion à la base winlog $db = mysql_pconnect($db_server, $db_user, $db_passwd); mysql_select_db($db_dbname, $db); $req_purge_machine = "TRUNCATE machines"; $req_purge_salle = "TRUNCATE salles"; $req_purge_compte = "TRUNCATE comptes"; // connexion LDAP à l'AD $ldap_con = ldap_connect($ldap_host, $ldap_port); $ldap_auth = ldap_bind($ldap_con, $ldap_rdn, $ldap_passwd); // Lecture des salles dans AD $res_salles = ldap_search($ldap_con, $base_salles, $filtre_salles, $attr_salles); $entry_salles = ldap_get_entries($ldap_con, $res_salles); // Insertion des machines mysql_query($req_purge_machine,$db); $salles = array(); // Ce tableau indexé contiendra les salles qui possèdent des machines for ($i=0; $i<$entry_salles["count"];$i++) { $dn_tab = ldap_explode_dn($entry_salles[$i]["dn"],1); $salle = $dn_tab[1]; // le nom de la salle dans laquelle elle se trouve est le 2° élément du DN d'une machine $salles[$i] = $salle; // tableau des salles construit à la volée $machine_id = $entry_salles[$i]["cn"][0]; $os = $entry_salles[$i]["operatingsystem"][0]; $os_sp = $entry_salles[$i]["operatingsystemservicepack"][0]; $os_version = $entry_salles[$i]["operatingsystemversion"][0];
$req_machine = "INSERT INTO machines (machine_id, salle, os, os_sp,
os_version) VALUES ('{$machine_id}', '{$salle}', '{$os}', '{$os_sp}',
'{$os_version}')"; mysql_query($req_machine,$db); } // Insertion des salles $salles = array_unique($salles); // suppression des doublons dans le tableau des salles mysql_query($req_purge_salle,$db); foreach($salles as $s) { $req_salle = "INSERT INTO salles (salle_id) VALUES ('{$s}')"; mysql_query($req_salle,$db); } // Lecture des enseignants dans AD $res_enseignants = ldap_search($ldap_con, $base_enseignants, $filtre_enseignants, $attr_enseignants); $entry_enseignants = ldap_get_entries($ldap_con, $res_enseignants); // Insertions des enseignants mysql_query($req_purge_compte,$db); for ($i=0; $i<$entry_enseignants["count"];$i++) { $dn_tab = ldap_explode_dn($entry_salles[$i]["dn"],1); $username = $entry_enseignants[$i]["samaccountname"][0]; $prenom = $entry_enseignants[$i]["givenname"][0]; $nom = $entry_enseignants[$i]["sn"][0];
$req_enseignant = "INSERT INTO comptes (username, prenom, nom, groupe)
VALUES ('{$username}', '{$prenom}', '{$nom}', 'Enseignant')"; mysql_query($req_enseignant,$db); } // Lecture des étudiants dans AD $res_etudiants = ldap_search($ldap_con, $base_etudiants, $filtre_etudiants, $attr_etudiants); $entry_etudiants = ldap_get_entries($ldap_con, $res_etudiants); // Insertions des étudiants for ($i=0; $i<$entry_etudiants["count"];$i++) { $dn_tab = ldap_explode_dn($entry_etudiants[$i]["dn"],1); $groupe = $dn_tab[1]; $username = $entry_etudiants[$i]["cn"][0]; $prenom = $entry_etudiants[$i]["givenname"][0]; $nom = $entry_etudiants[$i]["sn"][0];
$req_etudiant = "INSERT INTO comptes (username, prenom, nom, groupe)
VALUES ('{$username}', '{$prenom}', '{$nom}', '{$groupe}')"; mysql_query($req_etudiant,$db); } ldap_close($ldap_con); ?> |