.Net Core 2.2 - Générer un fichier users pour Freeradius à partir de MySQL

Publié le 2 septembre 2019

Sommaire

Introduction

Le but ici, est de générer un fichier plat pour Freeradius afin de réaliser une authentification par adresse MAC, celles-ci étant contenu dans une table MySQL.

Pour réaliser ceci, nous allons utiliser :

  • une base de données MySQL
  • le framework .Net Core 2.2

Vous pouvez récupérer le code source ici : https://gitlab.com/Flo0r/freeradius-mysql-generator

Pré-requis

Installation du SDK .Net Core 2.2

Il faut tout d’abord télécharger le SDK .Net Core 2.2, que vous trouverez ici : https://dotnet.microsoft.com/download/dotnet-core/sdk-for-vs-code

Construction de la base de données

Pour notre projet, nous utiliserons une base de données MySQL.

Création de la base de données

Tout d’abord, il faut créer la base de données, comme ceci :

CREATE DATABASE radiusManager COLLATE 'utf8_general_ci';

Création d’un utilisateur de connexion

CREATE USER 'DataIntegrator'@'localhost' IDENTIFIED BY 'MyPasswordNeedToBeSecure';

GRANT SELECT  ON *.* TO 'DataIntegrator'@'localhost';
FLUSH PRIVILEGES;

Notre utilisateur, dans ce cas, n’a besoin que de réaliser des opérations de lecture.

Création de la table d’autorisation des adresses MACs

Une fois notre base de données créée, nous allons nous concentrer sur la création de notre table contenant la correspondance entre nos utilisateurs et nos adresses MACs

CREATE TABLE `wifi_radius` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`macAddress` CHAR(17) NOT NULL,
	`vlan` SMALLINT(6) NOT NULL,
	`name` VARCHAR(50) NOT NULL,
	`firstName` VARCHAR(50) NOT NULL,
	`mail` VARCHAR(150) NOT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

Création d’utilisateurs

Désormais, il ne nous reste plus qu’à insérer nos utilisateurs dans notre base de données

INSERT INTO `wifi_radius` (`macAddress`, `vlan`, `name`, `firstName`, `mail`) VALUES ('00:56:00:01:02:03', 100, 'John', 'Doe', 'john.doe@null.net');
INSERT INTO `wifi_radius` (`macAddress`, `vlan`, `name`, `firstName`, `mail`) VALUES ('00:56:00:04:05:06', 200, 'Bob', 'Dylan', 'bob.dylan@null.net');

A ce moment, notre base de données est prête.

Construction de notre projet

Pour créer notre application, nous utilisons l’IDE Visual Studio Code avec le Terminal PowerShell intégré.

L’ensemble des commandes dotnet utilise ce Terminal.

Démarrage d’un projet .Net Core

Pour démarrer notre nouveau projet, nous allons réaliser une application Console, comme ceci :

dotnet new console

"dotnet new console"

Si nous regardons un peu les fichiers générés, nous avons un fichier .csproj, qui contient ceci :

"file .csproj"

Installation des dépendances

Pour que notre application fonctionne, nous utilisons un package qui nous permet d’utiliser MySQL (https://www.nuget.org/packages/MySql.Data/) :

dotnet add package MySql.Data --version 8.0.17

"dotnet add package"

Création d’une classe MySQL

Afin de gérer notre connexion à notre base de données ainsi que de récupérer nos données, nous allons créer une classe MySQL qui aura pour but de fournir une couche d’abstraction de la base de données.

En effet en créant une classe MySQL avec certains verbes génériques, ce sera plus facile de changer de type de base de données en créant une classe similaire avec les mêmes verbes.

using System;
using System.Data;
using MySql.Data.MySqlClient;

namespace Freeradius.GenerateUsersFileFromMySQL
{
	public class MYSQL
	{
		private string s_ConnexionString;
		private IDbConnection v_DbConnexion;
		private IDbCommand v_DbCommand;
		private string sqlRequest;

		private bool canStart = false;

		/** 
		Ouvre une connexion vers la base de données
		*/
		public MYSQL (string aConnexionString)
		{
			try {
				this.s_ConnexionString = aConnexionString;

				this.v_DbConnexion = new MySqlConnection(aConnexionString);
				this.v_DbConnexion.Open();

				canStart = true;
			} catch (MySqlException) {
				System.Console.WriteLine ("Can't connect to DB !");
			}
		}

		/** 
		Execute une requête et récupère les données
		 */
		public void ExecuteRequest(ref IDataReader p_Reader) 
		{
			if(!canStart)
				return;

			try {
				this.v_DbCommand = this.v_DbConnexion.CreateCommand ();
				this.v_DbCommand.CommandText = this.sqlRequest;
				p_Reader = this.v_DbCommand.ExecuteReader ();

			} catch (MySqlException p_Error) {
				Console.WriteLine (p_Error.ToString());

				return;
			}
		}
		/**
			Permet d'initialiser la requête SQL
		 */
		public void setRequest(string anSqlRequest) {
			this.sqlRequest = anSqlRequest;
		}

		~MYSQL() {
			this.v_DbCommand.Dispose();
			this.v_DbCommand = null;

			this.v_DbConnexion.Close();
			this.v_DbConnexion = null;
		}
	}
}

Une fois que l’on est capable d’intérroger notre base de données, il faut transformer nos données en fichier plat.

Création d’une classe DataIntegrator

Cette classe permet de récupérer des données d’une interface IDataReader en provenance d’une base de données ainsi que de les transformer au format Freeradius.

Elle a une structure assez classique en objet avec un Constructeur et un ensemble de méthode permettant de faire le travail demandé.

Le constructeur a 4 arguments, le premier est cette interface qui est passée par référence.

Les deux derniers arguments servent à gérer le format de l’adresse MAC de sortie, en effet selon les constructeurs, il y a plusieurs formats d’écriture des bornes.

using System;
using System.IO;
using System.Data;

namespace Freeradius.GenerateUsersFileFromMySQL
{
	public class DataIntegrator
    {
        String sPath = "";
        Char cMacSeparator;
        Char cMacSeparatorToReplace;
        IDataReader v_Reader = null;

        /**
            Constructeur du DataIntegrator
            p_Reader = référence vers l'interface de flux de données
            p_Path = Path vers le fichier à enregistrer
            p_MacSeparator & p_MacSeparatorToReplace = Séparateurs à remplacer selon le format attendu de l'adresse MAC
        */  
        public DataIntegrator(ref IDataReader p_Reader, String p_Path, Char p_MacSeparator, Char p_MacSeparatorToReplace = ':') {
            sPath = p_Path;
            cMacSeparator = p_MacSeparator; 
            cMacSeparatorToReplace = p_MacSeparatorToReplace; 
            v_Reader = p_Reader;
        }

        /**
        Permet de transformer des données sur la base d'un IDataReader et de les enregistrer dans un fichier selon le format Freeradius
         */
        public void Transform() {
            if(null == v_Reader) {
                return;
            }

			string generatedString = "";

			File.Delete (sPath);

			while (v_Reader.Read()) {

                if("" != generatedString) generatedString += "\n\n";

                generatedString +=  v_Reader[0].ToString().Replace(cMacSeparator, cMacSeparatorToReplace) + " Auth-Type := EAP\n" +
                       "        Tunnel-type = VLAN,\n" +
                       "        Tunnel-Medium-Type = 802,\n" +
                       "        Tunnel-Private-Group-ID = " + v_Reader[1].ToString();
			}

            // Inscription des données dans le fichier plat
            if (!File.Exists (sPath)) {
                File.WriteAllText (@sPath, generatedString);
            } else {
                File.AppendAllText (@sPath, generatedString);
            }
            // Une fois que l'on a autorisé les adresses MACs, les autres doivent être rejetée.
			if (File.Exists (sPath)) {
				File.AppendAllText (@sPath, "\n\nDEFAULT Auth-Type := Reject\n" 
					+ "        Reply-Message = \"Access rejected.\"");
			}

            Console.WriteLine ("Success file generation !");
        }

        /**
        Fermeture de l'itérateur
        */
        public void Close () {
            if(null != v_Reader) {
                v_Reader.Close();
            }
        }
	}
}

Et pour finir nous devons construire notre application avec les différentes instanciations.

Création de notre programme

Ici nous allons d’abord récupérer nos données provenant de notre base puis nous allons les traiter et les insérer dans un fichier plat.

using System.Data;

namespace Freeradius.GenerateUsersFileFromMySQL
{
	class MainClass
	{
		public static void Main (string[] args)
		{
            // Récupération des données de la base MySQL
            MYSQL myConnexion = new MYSQL("Server=localhost;Database=radiusManager;User ID=DataIntegrator;Password=MyBestPassword;");
			myConnexion.setRequest ("SELECT macAddress, vlan FROM `wifi_radius`;");
			IDataReader result = null;
            myConnexion.ExecuteRequest(ref result);

            // Transformation des données en fichier plat
            DataIntegrator integrate = new DataIntegrator(ref result, "/etc/freeradius/users", ':');
            integrate.Transform();
            integrate.Close();
		}
	}
}

Construction & exécution du programme

Nous pouvons construire notre programme afin de vérifier qu’il n’y a pas d’errreur :

dotnet build

"dotnet build"

Exécution de notre programme via la commande dotnet run :

dotnet run

"dotnet run"

Publication du programme

Une fois notre programme validé, il existe différents modes de déploiement d’une application .Net Core.

Ceux-ci sont listés ici : https://docs.microsoft.com/fr-fr/dotnet/core/deploying/deploy-with-cli

Nous ne verrons qu’un seul type, le déploiement de package autonome.

Déploiement du package autonome

Si nous regardons comment réaliser un package autonome nous avons cette syntaxe :

dotnet publish -c Release -r <RID> --self-contained true

Le RID correspond à la plateforme sur laquelle nous voulons déployer.

Nous pouvons trouver les différentes RIDs correspondant aux différentes plateformes ici : https://docs.microsoft.com/fr-fr/dotnet/core/rid-catalog

  • Pour réaliser une publication pour un environnement de production Microsoft :

dotnet publish -c Release -r win-x64 --self-contained true

"dotnet publish"

  • Pour réaliser une publication pour un environnement de production Linux :

dotnet publish -c Release -r linux-x64 --self-contained true

"dotnet publish"

Si nous regardons notre projet, nous voyons ceci dans le dossier bin\Release :

"files generated"

Nous voyons que pour chacune des plateformes, les binaires ont été générés avec l’ensemble des dépendances que nous avons utilisés.

Une fois publié, nous pouvons executer, en fonction de notre plateforme, notre executable, comme ceci :

"execution"