Introduction à Oracle Data Redaction

Introduction à Oracle Data Redaction

Cet article a pour but de présenter, via un cas de test, l’utilisateur de Oracle Data Redaction, fonctionnalité présente depuis la version 11.2.0.4 et qui fait partie des fonctionnalités de l’option Advanced Security.

L’idée derrière cette fonctionnalité est de permettre la réécriture à la volée de certaines données critiques avant leur affichage, dans une perspective de confidentialité accrue. Cet article va s’intéresser à un cas concret,
la mise en place de restrictions sur l’affichage des numéros de cartes bancaires dans une table.

Préparation du cas de test

En tant que SYS, création d’un utilisateur de test :

CREATE USER EASYTEST IDENTIFIED BY secret;
GRANT resource,connect to EASYTEST;
GRANT UNLIMITED TABLESPACE TO EASYTEST;

Il faut à minima donner à l’utilisateur le droit d’exécution sur sys.dbms_redact pour qu’il puisse créer/modifier/supprimer des policies de rédaction :

GRANT EXECUTE ON sys.dbms_redact TO EASYTEST;
GRANT SELECT ON sys.redaction_policies TO EASYTEST;
GRANT SELECT ON sys.redaction_columns TO EASYTEST;
GRANT SELECT ON sys.redaction_values_for_type_full TO EASYTEST;

Création de la table CLIENT pouvant stocker les numéros de cartes des clients dans un champ VARCHAR2 sous la forme « XXXX XXXX XXXX XXXX » :

connect EASYTEST;
CREATE TABLE CLIENTS (
id number PRIMARY KEY,
nom varchar2(128),
prenom varchar2(128),
num_cb varchar2(20),
code_securite number(3),
expiration date
);

Alimentation de la table avec un jeu de données :

INSERT INTO CLIENTS VALUES (1, 'Brian', 'Charles','4556 5312 7506 7894','469',TO_DATE(TRUNC(DBMS_RANDOM.VALUE(TO_CHAR(DATE '2018-01-01','J'),TO_CHAR(DATE '2022-12-31','J'))),'J'));
INSERT INTO CLIENTS VALUES (2, 'Vallet', 'Emmanuelle','4916 4540 7789 3521','954',TO_DATE(TRUNC(DBMS_RANDOM.VALUE(TO_CHAR(DATE '2018-01-01','J'),TO_CHAR(DATE '2022-12-31','J'))),'J'));
INSERT INTO CLIENTS VALUES (3, 'Achin','Fabrice','4024 0071 0735 1941','422',TO_DATE(TRUNC(DBMS_RANDOM.VALUE(TO_CHAR(DATE '2018-01-01','J'),TO_CHAR(DATE '2022-12-31','J'))),'J'));
INSERT INTO CLIENTS VALUES (4, 'Lampron','Paul','4716 0106 2214 4570','154',TO_DATE(TRUNC(DBMS_RANDOM.VALUE(TO_CHAR(DATE '2018-01-01','J'),TO_CHAR(DATE '2022-12-31','J'))),'J'));
INSERT INTO CLIENTS VALUES (5, 'Alfred','Sylvain','4024 0071 6531 9970','843',TO_DATE(TRUNC(DBMS_RANDOM.VALUE(TO_CHAR(DATE '2018-01-01','J'),TO_CHAR(DATE '2022-12-31','J'))),'J'));
commit;

Mise en place de policies DBMS_REDACT

A ce stade, sans surprise, le champs NUM_CB est visible dans la table lorsqu’on la requête :

set line 300;
col nom for a15;
col prenom for a15;
col NUM_CB for a20;
select * from easytest.clients;

ID NOM             PRENOM          NUM_CB               CODE_SECURITE EXPIRATIO
-- --------------- --------------- -------------------- ------------- ---------
 1 Brian           Charles         4556 5312 7506 7894            469 18-DEC-18
 2 Vallet          Emmanuelle      4916 4540 7789 3521            954 19-NOV-21
 3 Achin           Fabrice         4024 0071 0735 1941            422 01-NOV-20
 4 Lampron         Paul            4716 0106 2214 4570            154 19-JUN-22
 5 Alfred          Sylvain         4024 0071 6531 9970            843 29-APR-20

Admettons désormais que l’on souhaite que le champ soit masqué lors d’un SELECT sur la table. Pour ce faire, il faut mettre en place une politique DBMS_REDACT qui s’appuie sur la fonction DBMS_REDACT.full, comme suit :

BEGIN
  DBMS_REDACT.add_policy(
    object_schema => 'easytest', 			-- Nom du schéma cible
    object_name   => 'clients',	 			-- Nom de la table cible
    column_name   => 'num_cb',   			-- Nom du champs cible
    policy_name   => 'redact_num_cc',		-- Nom de la policy que l'on souhaite créer
    function_type => DBMS_REDACT.full,		-- Fonction à utiliser dans cette policy
    expression    => '1=1'					-- Règle d'application, ici, 1=1 donc toujours active
  );
END;
/

Si l’on requête à nouveau la table, on s’aperçoit que le champs NUM_CB ne s’affiche plus, il est masqué par la nouvelle policy :

select * from easytest.clients;

        ID NOM             PRENOM          NUM_CB               CODE_SECURITE EXPIRATIO
---------- --------------- --------------- -------------------- ------------- ---------
         1 Brian           Charles                                        469 13-DEC-20
         2 Vallet          Emmanuelle                                     954 28-SEP-19
         3 Achin           Fabrice                                        422 12-SEP-18
         4 Lampron         Paul                                           154 01-JAN-19
         5 Alfred          Sylvain                                        843 18-MAR-20

Il est possible d’afficher les policies actuellement en place :

col OBJECT_OWNER for a15;
col OBJECT_NAME for a15;
col EXPRESSION for a40;
col POLICY_DESCRIPTION for a60;
SELECT * FROM sys.redaction_policies;
OBJECT_OWNER    OBJECT_NAME     POLICY_NAME                    EXPRESSION                               ENABLE  POLICY_DESCRIPTION
--------------- --------------- ------------------------------ ---------------------------------------- ------- ------------------
EASYTEST        CLIENTS         redact_num_cc                  1=1                                      YES

Ici, il s’agit d’une politique DBMS_REDACT.full, qui cache le champ dans son intégralité. Il est également possible de définir une policy de type DBMS_REDACT.partial qui peut cacher seulement une partie du champ.

Imaginons maintenant que l’on souhaite masquer seulement les 3 premiers groupes de digits du numéro de carte en les remplaçant par des astérisques et avec un affichage du numéro de carte au format « ****-****-****-XXXX »

BEGIN
  DBMS_REDACT.drop_policy (
    object_schema => 'easytest',
    object_name   => 'clients',
    policy_name   => 'redact_num_cc'
  );
END;
/

BEGIN
  DBMS_REDACT.add_policy(
    object_schema => 'EASYTEST',
    object_name   => 'CLIENTS',
    column_name   => 'NUM_CB',
    policy_name   => 'redact_num_cc',
    function_type => DBMS_REDACT.partial,
	expression	  => '1=1',
	function_parameters => 'VVVVFVVVVFVVVVFVVVV,VVVV-VVVV-VVVV-VVVV,*,1,12'
  );
END;
/

Plutôt que supprimer/recréer la policy, il est également possible de la modifier. Aussi, on aurait pu arriver au même résultat avec :

BEGIN
  DBMS_REDACT.alter_policy (
    object_schema       => 'EASYTEST',
    object_name         => 'CLIENTS',
	column_name         => 'NUM_CB',
    policy_name         => 'redact_num_cc',
    function_type 		=> DBMS_REDACT.partial,
	action				=> DBMS_REDACT.MODIFY_COLUMN,
	expression	  		=> '1=1',
	function_parameters => 'VVVVFVVVVFVVVVFVVVV,VVVV-VVVV-VVVV-VVVV,*,1,12'
  );
END;
/

Noter le paramètre supplémentaire action => DBMS_REDACT.MODIFY_COLUMN qui est nécessaire dans le cas d’une modification de policy. Le paramètre function_parameters, lui, permet de définir les règles de réécriture :

  • VVVVFVVVVFVVVVFVVVV : Format d’entrée. Les « V » représente des caractères de valeur, soit les digits de notre code de carte. Les « F » des caractères de formatage, soit ici les espaces entre chaque bloc de 4 caractères.
  • VVVV-VVVV-VVVV-VVVV : Format de sortie. Les « V » représente des caractères de valeur. Entre chaque bloc de 4 caractères, on utilise ici des tirets (-)
  • * : Caractère de remplacement pour les caractères masqués
  • 1 : Caractère de début du masquage
  • 12 : Nombre de caractères masqués à partir du caractère de début

Une requête sur la table retourne le champ NUM_CB au format demandé :

select * from easytest.clients;

        ID NOM             PRENOM          NUM_CB               CODE_SECURITE EXPIRATIO
---------- --------------- --------------- -------------------- ------------- ---------
         1 Brian           Charles         ****-****-****-7894            469 13-DEC-20
         2 Vallet          Emmanuelle      ****-****-****-3521            954 28-SEP-19
         3 Achin           Fabrice         ****-****-****-1941            422 12-SEP-18
         4 Lampron         Paul            ****-****-****-4570            154 01-JAN-19
         5 Alfred          Sylvain         ****-****-****-9970            843 18-MAR-20

On peut également imaginer avoir besoin de masquer les numéros de carte uniquement lors de l’accès depuis un autre utilisateur Oracle. On peut spécifier via le paramètre « expression » une condition d’application de la règle. Si l’on veut que seul le user EASYTEST puisse lire les numéro de carte, il convient de modifier la policy comme suit :

BEGIN
  DBMS_REDACT.alter_policy (
    object_schema       => 'EASYTEST',
    object_name         => 'CLIENTS',
	column_name         => 'NUM_CB',
    policy_name         => 'redact_num_cc',
    function_type 		=> DBMS_REDACT.partial,
	action				=> DBMS_REDACT.MODIFY_COLUMN,
	expression    => 'SYS_CONTEXT(''USERENV'',''SESSION_USER'') != ''EASYTEST''',
	function_parameters => 'VVVVFVVVVFVVVVFVVVV,VVVV-VVVV-VVVV-VVVV,*,1,12'
  );
END;
/

Seul EASYTEST et les utilisateurs disposant du privilège EXEMPT REDACTION POLICY (typiquement, SYS) peuvent afficher les numéros de carte avec cette policy en place :

show user;
USER is "EASYTEST"
select * from easytest.clients;

        ID NOM             PRENOM          NUM_CB               CODE_SECURITE EXPIRATIO
---------- --------------- --------------- -------------------- ------------- ---------
         1 Brian           Charles         4556 5312 7506 7894            469 13-DEC-20
         2 Vallet          Emmanuelle      4916 4540 7789 3521            954 28-SEP-19
         3 Achin           Fabrice         4024 0071 0735 1941            422 12-SEP-18
         4 Lampron         Paul            4716 0106 2214 4570            154 01-JAN-19
         5 Alfred          Sylvain         4024 0071 6531 9970            843 18-MAR-20

— > EASYTEST est l’exception dans la clause « expression » : le champ n’est pas réécrit

connect / as sysdba
show user;
USER is "SYS"
select * from easytest.clients;

        ID NOM             PRENOM          NUM_CB               CODE_SECURITE EXPIRATIO
---------- --------------- --------------- -------------------- ------------- ---------
         1 Brian           Charles         4556 5312 7506 7894            469 13-DEC-20
         2 Vallet          Emmanuelle      4916 4540 7789 3521            954 28-SEP-19
         3 Achin           Fabrice         4024 0071 0735 1941            422 12-SEP-18
         4 Lampron         Paul            4716 0106 2214 4570            154 01-JAN-19
         5 Alfred          Sylvain         4024 0071 6531 9970            843 18-MAR-20

— > SYS dispose du privilège EXEMPT REDACTION POLICY : le champ n’est pas réécrit

-- Création d'un nouvel utilisateur pour tester la réécriture.
create user easytest_2 identified by secret;
grant resource,connect to secrest;
grant select on easytest.clients to easytest2;
connect easytest2;
select * from easytest.clients;

        ID NOM             PRENOM          NUM_CB               CODE_SECURITE EXPIRATIO
---------- --------------- --------------- -------------------- ------------- ---------
         1 Brian           Charles         ****-****-****-7894            469 13-DEC-20
         2 Vallet          Emmanuelle      ****-****-****-3521            954 28-SEP-19
         3 Achin           Fabrice         ****-****-****-1941            422 12-SEP-18
         4 Lampron         Paul            ****-****-****-4570            154 01-JAN-19
         5 Alfred          Sylvain         ****-****-****-9970            843 18-MAR-20

— > Bien que disposant du droit de lecture sur la table, le champ NUM_CB est réecrit pour l’utilisateur EASYTEST2.

Cette policy améliore la sécurité de la table en ne permettant pas l’affichage des numéros de carte malgré le privilège SELECT sur la table, et ce, à l’exception du proprietaire de la table. Le cas de test est basique, mais la fonctionnalité est très souple et permet une gestion très fine des politiques de sécurité.
Voir la documentation de Oracle Data Redaction pour appréhender les sujets plus complexes.

Nettoyage du cas de test

En tant que SYS :

BEGIN
  DBMS_REDACT.drop_policy (
    object_schema => 'easytest',
    object_name   => 'clients',
    policy_name   => 'redact_num_cc'
  );
END;
/
drop user EASYTEST cascade;
drop user EASYTEST2 cascade;