Dans mon précédent article je vous expliquais comment gérer la protection des données personnelles en tant que développeur Azure, nous avons vu la réglementation RGPD ainsi que l’impact qu’elle peut avoir sur les développeurs.

Dans ce nouvel article, nous allons voir comment mettre en place le Dynamic Data Masking (DDM) sur Azure et comment le prendre en compte dans notre code.

Dans un premier temps, nous explorerons l’ensemble des fonctionnalités du Dynamic Data Masking ainsi que les différentes limites qu’il peut rencontrer. Pour illustrer ces fonctionnalités, nous allons notamment nous appuyer sur .NET Framework (version 4.5.2) et Entity Framework (version 6.2.0). En revanche il est important de noter que ces fonctionnalités sont bien inhérentes aux Dynamic Data Masking, ainsi le langage de code utilisé n’a pas d’importance.

Le Dynamic Data Masking est disponible sur Azure SQL Database ainsi que sur SQL Server à partir de la version 2016 (13.x).

 

 

Le Dynamic Data Masking, c’est quoi ?

 

Le Dynamic Data Making permet de limiter l’exposition de données sensibles en les masquant aux utilisateurs. Cette technologie aide à prévenir les accès non autorisés et cela à des données sensibles.

Le Dynamic Data Masking est appliqué sur les résultats des requêtes, il n’y a ici donc pas de modification physique de la donnée.

Ainsi des données sensibles pourraient ressembler à cela avec le masquage actif :

masquage des données sensibles

 

 

La mise en place du Dynamic Data Masking

 

Le Dynamic Data Masking n’a pas besoin d’installation particulière ou de l’activation d’un paramètre. Pour l’utilisation de celui-ci, il suffit de préciser la ou les colonnes que nous voulons masquer. Il est d’ailleurs possible de préciser ces colonnes à différents moments. Cette précision peut se faire à l’aide d’une requête SQL ou via le portail Azure.

Avec une requête SQL, nous pouvons ajouter le masquage d’une colonne lors de la création de la table :

 

Ou bien juste en ajoutant le masquage directement sur une colonne :

 

Ainsi pour ajouter un masquage, nous allons utiliser la phrase « MASKED WITH (FUNCTION = ‘[…]’) », la partie qui va permettre de spécifier le masquage est celle entre crochets.

Voici les quatre fonctions disponibles pour le masquage :

  • la fonction default(): elle masque complètement la donnée en fonction de son type.
    • string: la donnée est remplacée par quatre X au maximum : « XXXX », si le champ permet moins de quatre caractères alors le nombre de X sera égal au nombre de caractères disponibles.
    • numeric : la donnée est remplacée par un zéro.
    • date et time : la date est remplacée par le 1er Janvier 1900 et l’heure par minuit.
  • la fonction partial([préfixe], “[remplissage]“, [suffixe]): cette fonction permet de prendre les « [préfixe] » premiers caractères de la valeur réelle et les « [suffixe] » derniers caractères de la valeur réelle et d’ajouter entre les deux les caractères définis dans « [remplissage] ». Par exemple, pour la valeur « Stéphane » avec la fonction « partial(2, “XXXX”, 1) » cela donne : « StXXXXe ».
  • la fonction email(): cette fonction correspond à « partial(1, XXX@XXXX.com,0) » et permet donc de remplacer la valeur par la première lettre de sa valeur suivi de XXX@XXXX.com, utilisé pour masquer les adresses email. Par exemple, l’adresse « exemple@contoso.eu » donne « eXXX@XXXX.com ».
  • et enfin la fonction random([borne inférieure], [borne supérieure]): permet de masquer un champ numérique par une valeur aléatoire entre la [borne inférieure] et la [borne supérieur] incluse. Pour vous donner un exemple, avec la fonction « random(1,100) » la colonne numérique est masquée par une valeur pouvant aller de 1 à 100. La valeur affichée est du même type que celle de la colonne, ainsi si la colonne accepte des nombres avec des décimales alors la valeur aléatoire pourra avoir des nombres avec des décimales et inversement.

En passant par le portail Azure, il est également possible de mettre en place le Dynamic Data Masking. Pour cela, il suffit d’aller sur une ressource correspondant à une « Base de données SQL », puis d’aller sur la fonction « Dynamic Data Masking », dans la partie « Sécurité ». Cette fonction permet de visualiser les masquages créés, de créer des masquages, d’en supprimer et d’avoir des recommandations sur les champs qu’il faudrait masquer.

En créant un masquage via le portail Azure, nous avons le choix entre cinq possibilités : les quatre précédemment expliquées et une cinquième la « Valeur de la carte de crédit ». Cette cinquième possibilité correspond à la fonction « partial(0, “xxxx-xxxx-xxxx-“, 4) » et comme son nom l’indique elle est utilisée pour le masquage des numéros de carte de crédit.

capture écran de la valeur partial

 

 

La gestion des données réelles et masquées

 

Comme pour toutes les gestions des utilisateurs sur une base SQL, un nouveau privilège a été créé pour la gestion de l’affichage des données réelles/masquées. Ce droit s’appelle « UNMASK », il peut être ajouté via une requête SQL ou via le portail Azure.

 

capture ecran maskingdb

 

Pour supprimer le droit « UNMASK » il suffit de retirer l’utilisateur de la liste dans le portail Azure ou de lui révoquer le privilège via la requête SQL :

 

 

Le fonctionnement du masquage dans votre code

 

Pour le vérifier autant le tester directement. Pour cela, nous avons besoin d’une base de données, nous allons donc créer une ressource « SQL Database » (il est également possible d’utiliser SQL Server) voici les trois étapes à suivre :

  1. Aller sur le portail Azure,
  2. Créer une ressource « SQL Database »,
  3. Vous êtes libre de mettre les paramètres que vous souhaitez, voici les paramètres avec lesquels nous allons travailler dans cet article :

paramètres utilisés

 

Pour limiter les coûts, nous avons sélectionné la configuration la moins chère étant donné que nous ne voulons tester que les différentes fonctionnalités du Dynamic Data Masking, les performances nous important peu.

Une fois la ressource créée, il faut pouvoir y accéder à partir de notre machine. Voici le chemin à suivre pour y arriver :

  1. Aller sur le « Server SQL » fraîchement créé, ici « rgpd-test »,
  2. Cliquer sur « Pare-feux et réseaux virtuels » dans la partie « Sécurité »,
  3. Cliquer sur « Ajouter une adresse IP cliente » (1), une ligne va s’ajouter dans les règles avec votre IP publique (2),
  4. Cliquer sur « Enregistrer » (3).

Capture écran : Accès vers la ressource

 

Il nous reste maintenant à configurer la base de données. À l’aide de votre requêteur préféré, lancer les requêtes suivantes, celles-ci vont permettre de créer un utilisateur « mask_test » avec le mot de passe « Azerty1234 », lui donner les droits de lecture puis de créer une table « Person » avec différentes colonnes masquées puis d’y insérer des valeurs de test :

Notre base de données est maintenant prête, il nous reste à faire notre code pour interroger celle-ci :

  • Démarrer Visual Studio (ou l’IDE que vous souhaitez),
  • Créer un projet Console,
  • Ajouter le package NuGet « EntityFramework »,
  • Ajouter un nouvel élément « Data -> ADO.NET Entity Data Model » avec le nom « MaskContext »,

Capture d'ecran MaskContext

  • Sélectionner « Code First à partir de la base de données »,

Capture d'écran Code first à partir de la base de données

  • Créer une nouvelle connexion vers votre base de données avec votre utilisateur administrateur. Il n’est pas possible d’utiliser l’utilisateur « mask_test » ici car celui-ci ne peut pas accéder à la base de données « master »,
  • Cliquer sur « Oui, inclure les données sensibles dans la chaîne de connexion”. Attention, cela va mettre le mot de passe en clair dans la chaîne de connexion,
  • Cocher la case « Enregistrer les paramètres de connexion dans App.Config en tant que : « MaskContext »,

Capture d'écran étape 8

  • Sélectionner la table « Person » pour l’inclure dans notre modèle,

Capture d'écran étape 9

  • Cliquer sur Terminer.

 

À ce moment, il y aura eu trois modifications :

  • La création du fichier « MaskContext.cs » qui permet de gérer la connexion et les requêtes sur la base de données,
  • La création du fichier « Person.cs » qui décrit le modèle de la table « Person »,
  • L’ajout de la chaîne de connexion dans le fichier « App.config » :

 

Modifier « user id » par « mask_test » et le « password » par « Azerty1234 » :

 

Il reste alors à coder la récupération des personnes et de les afficher, dans la fonction main :

 

Il ne reste plus qu’à exécuter le code pour voir le résultat :

capture code

Le masquage fonctionne est bel et bien dans le code. Nous remarquons aussi que nous avons oublié de masquer le numéro de carte bancaire, il nous suffit de passer sur le portail Azure pour corriger cela (il est également possible de le faire avec une requête SQL). Nous pouvons aussi voir qu’Azure nous recommande d’ajouter un masquage sur la colonne « LastName », en effet le prénom et le nom d’une personne peuvent permettre d’identifier une personne unique (nous n’avons pas activé ce masque dans cet article).

Relançons notre programme pour vérifier que le masquage de la carte de crédit est pris en compte :

Capture code masquage de la carte de crédit

Nous pouvons constater que le nombre de voitures a changé entre les deux exécutions et vous avez probablement des nombres différents ce qui correspond bien au masquage « random ».

 

 

La gestion des données non masquées

 

Nous avons constaté que pour voir les données non masquées, il faut avoir le privilège « UNMASK », nous avons donc deux possibilités :

  • ajouter le privilège à l’utilisateur « mask_test », ou,
  • utiliser un utilisateur qui possède ce privilège.

Par exemple, si nous modifions notre chaîne de connexion pour mettre le compte administrateur et que nous relançons le code :

Code modification chaîne de connexion pour mettre le compte administrateur

Nous obtenons alors correctement les données non masquées.

 

 

Accédez à vos données masquées et non masquées

 

En effet, dans nos traitements nous voudrions parfois avoir les données masquées et parfois les données non masquées. Pour se faire, il faut avoir deux chaînes de connexion avec pour chacune d’entre elles la même base de données mais un utilisateur différent.

Cependant, nous voulons éviter de dupliquer le code et ainsi avoir deux contextes : un pour les données non masquées et un pour les données masquées (attention cela peut rendre la maintenance du code plus complexe). Dans ce cas, créons un seul contexte qui change sa chaîne de connexion en fonction des besoins :

À partir du moment où le constructeur de la classe mère est appelé, la connexion à la base de données est établie. Il faut donc changer la chaîne de connexion avant que la connexion ne soit faite, d’où la création des méthodes statiques pour l’initialisation et le passage du constructeur en « private ».

À ce stade, il nous reste plus qu’à gérer l’encryptage du mot de passe mais ceci n’est pas le but de cet article.

Il nous reste alors à modifier notre programme en conséquence :

Puis en exécutant le programme, nous obtenons :

Capture code

Nous avons ainsi accès aux données masquées et non masquées dans notre code et un seul contexte Entity Framework à gérer.

 

 

La mise à jour des données à partir d’un utilisateur qui ne voit pas les données réelles

 

ATTENTION : ce cas est considéré comme une mauvaise pratique, pour insérer/mettre à jour des données, il faut utiliser un utilisateur qui voit les données réelles. La suite de ce paragraphe est donc purement informel et il ne faut surtout pas concevoir des applications avec ce mode de fonctionnement.

 

Pour tester cette fonctionnalité, ajoutons les droits d’écriture à notre utilisateur « mask_test » :

 

Puis créons une nouvelle personne dans notre code avant d’afficher l’ensemble des personnes :

 

Nous passons ici par un nouveau context (insertMaskDb) puisque sinon EntityFramework ne va pas chercher la nouvelle personne en base de données étant donné qu’il la possède déjà.

Puis exécutons le programme pour voir le résultat (il est possible que vous obteniez une erreur liée à un manque de l’identifiant de la nouvelle personne, vérifier que dans le fichier Person.cs l’attribut « Id » a bien l’option « DatabaseGeneratedOption.Identity » :

Puis en lançant le programme, nous obtenons donc :

Capture code

Les données sont donc correctement insérées avec les bonnes valeurs, le masquage de données est quant à lui actif lors de la sélection. C’est pour cette raison qu’il est préférable d’éviter de donner les droits d’écriture à une personne qui voit les données masquées car celle-ci peut modifier une valeur alors qu’elle ne les voit pas.

 

 

Les requêtes sur les colonnes masquées

 

Imaginons que nous voulons récupérer les personnes qui ont moins de 60 ans. Est-ce qu’il est possible de les récupérer via l’utilisateur qui ne voit pas les données réelles ?

Modifions notre requête de sélection dans notre code pour vérifier cela :

Puis nous lançons le programme pour voir le résultat :

capture code

La requête renvoie bien le bon résultat même si l’utilisateur ne voit pas les données réelles. En effet, nous avons appris au début de cet article que le Dynamic Data Masking est effectué sur les résultats des requêtes ainsi peu importe que l’utilisateur voit les données réelles ou non.

À l’inverse, si un utilisateur qui ne voit pas les données réelles fait une requête pour effectuer une copie de données vers une table sans masquage, alors ce sont les données masquées qui seront copiées. De même, si cet utilisateur exporte une table avec masquage alors ce sont les données masquées qui sont exportés.

 

 

Les limites du Dynamic Data Masking ?

 

À l’heure actuelle, le Dynamic Data Masking possède cinq limitations :

  • Une règle de masquage ne peut pas être définie sur une colonne encryptée,
  • Une règle de masquage ne peut pas être définie sur une colonne de type « FILESTREAM » et/ou « COLUMN_SET »,
  • Un masque ne peut pas être établi sur une colonne résultante d’une opération entre d’autres colonnes. En revanche si une des colonnes utilisées pour créer cette colonne est masquée alors la colonne résultante sera également masquée.
  • Une colonne masquée ne peut pas être utilisée comme clé pour un index FULLTEXT.
  • Il n’est pas possible d’ajouter un masquage sur une colonne qui a des dépendances. Il faudra retirer ces dépendances avant de pouvoir ajouter le masquage puis recréer les dépendances.

Une autre chose importante à savoir : le Dynamic Data Masking empêche la diffusion de données sensibles mais ne la protège pas contre des intentions malveillantes. En effet, nous avons vu qu’il était possible de faire des requêtes sur les données réelles même si l’utilisateur ne les voit pas. Celui-ci peut donc engager une technique de « brute-force » ou d’inférence pour deviner la véritable valeur.

 

 

Et au niveau des performances ?

 

Le Dynamic Data Masking est une surcouche qui va se lancer sur les résultats de requête, cela peut donc impacter les performances d’une requête. J’ai donc effectué quelques tests en remplissant notre table « Person » avec 1 millions d’entrées et j’ai comparé le temps nécessaire pour récupérer ces données pour un utilisateur qui voit les données masquées avec un utilisateur qui voit les données réelles. Pour les données réelles, la requête pour récupérer l’ensemble des valeurs prenait environ 18 secondes, tandis que pour les données masquées, la requête s’exécutait en environ 20 secondes soit environ 10% plus lente (ce test a été effectué 20 fois sur chacune des requêtes).

Par conséquent, la performance de requête est réellement impactée que pour des requêtes ayant des résultats avec au moins 100 000 lignes (avec notre base actuelle, si nous avions pris une configuration plus performante, ce nombre serait certainement plus haut). Cependant, dans un programme informatique, ce type de requête ne devrait probablement jamais avoir lieu avec les données masquées.

 

 

En quoi le Dynamic Data Masking est utile pour la RGPD ?

 

Le Dynamic Data Masking permet d’apporter une couche de sécurité supplémentaire utile pour répondre à la réglementation sur la sécurité et l’intégrité des données imposées par la RGPD. Cependant le Dynamic Data Masking ne suffit pas pour répondre à cette réglementation. En effet, nous avons vu que cette technologie ne permet pas de répondre aux intentions malveillantes, elle permet juste d’assurer la sécurité des données en cas de « fuite ». Le Dynamic Data Masking va également permettre d’assurer que des personnes sans privilège n’accèdent pas à des données sensibles.

De plus pour être en respect de la RGPD, lorsque nous développons et que nous sommes obligés d’utilisér la connexion à la base de données sans masquage pour récupérer des données, nous devons nous demander si l’utilisateur nous a donné sa permission pour le traitement que nous allons faire avec ces données.

 

 

Pour conclure

 

Le Dynamic Data Masking est une technologie simple et qui ne prend pas plus que quelques minutes à mettre en place dans un projet informatique et qui permet d’ajouter une couche de sécurité pour les données personnelles.

Aussi, nous devrions l’intégrer à tous nos projets ayant des données personnelles et/ou sensibles !