Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.
Meilleures pratiques pour une implémentation sécurisée de pg_columnmask
La section suivante présente les meilleures pratiques de sécurité pg_columnmask à mettre en œuvre dans votre environnement Aurora PostgreSQL. Suivez ces recommandations pour :
Mettre en place une architecture de contrôle d'accès sécurisée basée sur les rôles
Développez des fonctions de masquage qui préviennent les failles de sécurité
Comprenez et contrôlez le comportement des déclencheurs à l'aide de données masquées
Architecture de sécurité basée sur les rôles
Définissez une hiérarchie des rôles pour implémenter des contrôles d'accès dans votre base de données. Aurora pg_columnmask PostgreSQL renforce ces contrôles en fournissant une couche supplémentaire pour un masquage précis des données au sein de ces rôles.
Créez des rôles dédiés qui correspondent aux fonctions de l'organisation plutôt que d'accorder des autorisations à des utilisateurs individuels. Cette approche améliore l'auditabilité et simplifie la gestion des autorisations au fur et à mesure de l'évolution de votre structure organisationnelle.
Exemple de créer une hiérarchie des rôles organisationnels
L'exemple suivant crée une hiérarchie des rôles organisationnels avec des rôles dédiés à différentes fonctions, puis affecte des utilisateurs individuels aux rôles appropriés. Dans cet exemple, les rôles organisationnels (analyst_role, support_role) sont créés en premier, puis les utilisateurs individuels obtiennent l'adhésion à ces rôles. Cette structure vous permet de gérer les autorisations au niveau du rôle plutôt que pour chaque utilisateur individuel.
-- Create organizational role hierarchy CREATE ROLE data_admin_role; CREATE ROLE security_admin_role; CREATE ROLE analyst_role; CREATE ROLE support_role; CREATE ROLE developer_role; -- Specify security_admin_role as masking policy manager in the DB cluster parameter -- group pgcolumnmask.policy_admin_rolname = 'security_admin_role' -- Create specific users and assign to appropriate roles CREATE USER security_manager; CREATE USER data_analyst1, data_analyst2; CREATE USER support_agent1, support_agent2; GRANT security_admin_role TO security_manager; GRANT analyst_role TO data_analyst1, data_analyst2; GRANT support_role TO support_agent1, support_agent2;
Mettez en œuvre le principe du moindre privilège en n'accordant que les autorisations minimales nécessaires pour chaque rôle. Évitez d'accorder des autorisations étendues qui pourraient être exploitées si les informations d'identification sont compromises.
-- Grant specific table permissions rather than schema-wide access GRANT SELECT ON sensitive_data.customers TO analyst_role; GRANT SELECT ON sensitive_data.transactions TO analyst_role; -- Do not grant: GRANT ALL ON SCHEMA sensitive_data TO analyst_role;
Les administrateurs de politiques ont besoin de USAGE privilèges sur les schémas dans lesquels ils gèrent les politiques de masquage. Accordez ces privilèges de manière sélective, conformément au principe du moindre privilège. Révisez régulièrement les autorisations d'accès aux schémas pour vous assurer que seul le personnel autorisé dispose des capacités de gestion des politiques.
La configuration des paramètres du rôle d'administrateur des politiques est réservée aux administrateurs de base de données uniquement. Ce paramètre ne peut pas être modifié au niveau de la base de données ou de la session, ce qui empêche les utilisateurs non privilégiés de remplacer les attributions des administrateurs de politiques. Cette restriction garantit que le contrôle des politiques de masquage reste centralisé et sécurisé.
Attribuez le rôle d'administrateur des politiques à des personnes spécifiques plutôt qu'à des groupes. Cette approche ciblée garantit un accès sélectif à la gestion des politiques de masquage, car les administrateurs de politiques ont la possibilité de masquer toutes les tables de la base de données.
Développement de fonctions de masquage sécurisées
Développez des fonctions de masquage à l'aide d'une sémantique de liaison précoce afin de garantir un suivi correct des dépendances et de prévenir les vulnérabilités liées aux liaisons tardives, telles que la modification du chemin de recherche pendant l'exécution. Il est recommandé d'utiliser la BEGIN ATOMIC syntaxe des fonctions SQL afin de permettre la validation au moment de la compilation (c'est-à-dire la liaison anticipée) et la gestion des dépendances.
-- Example - Secure masking function with early binding CREATE OR REPLACE FUNCTION secure_mask_ssn(input_ssn TEXT) RETURNS TEXT LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT BEGIN ATOMIC SELECT CASE WHEN input_ssn IS NULL THEN NULL WHEN length(input_ssn) < 4 THEN repeat('X', length(input_ssn)) ELSE repeat('X', length(input_ssn) - 4) || right(input_ssn, 4) END; END;
Vous pouvez également créer des fonctions immunisées contre les modifications du chemin de recherche en qualifiant explicitement toutes les références d'objets selon le schéma, afin de garantir un comportement cohérent entre les différentes sessions utilisateur.
-- Function immune to search path changes CREATE OR REPLACE FUNCTION data_masking.secure_phone_mask(phone_number TEXT) RETURNS TEXT LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT AS $$ SELECT CASE WHEN phone_number IS NULL THEN NULL WHEN public.length(public.regexp_replace(phone_number, '[^0-9]', '', 'g')) < 10 THEN 'XXX-XXX-XXXX' ELSE public.regexp_replace( phone_number, '([0-9]{3})[0-9]{3}([0-9]{4})', public.concat('\1-XXX-\2') ) END; $$;
Implémentez la validation des entrées dans les fonctions de masquage afin de gérer les cas extrêmes et d'éviter les comportements inattendus. Incluez toujours la gestion des valeurs NULL et validez les formats d'entrée pour garantir un comportement de masquage cohérent.
-- Robust masking function with comprehensive input validation CREATE OR REPLACE FUNCTION secure_mask_phone(phone_number TEXT) RETURNS TEXT LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT BEGIN ATOMIC SELECT CASE WHEN phone_number IS NULL THEN NULL WHEN length(trim(phone_number)) = 0 THEN phone_number WHEN length(regexp_replace(phone_number, '[^0-9]', '', 'g')) < 10 THEN 'XXX-XXX-XXXX' ELSE regexp_replace(phone_number, '([0-9]{3})[0-9]{3}([0-9]{4})', '\1-XXX-\2') END; END;
Comportement des déclencheurs DML avec pg_columnmask
Pour les déclencheurs de tables, les tables de transition seront entièrement démasquées. Pour les déclencheurs de vue (IOT), les tables de transition seront masquées en fonction des autorisations de consultation de l'utilisateur actuel.
- Déclencheurs de table avec pg_columnmask
Les déclencheurs reçoivent une table de transition contenant l'ancienne et la nouvelle version des lignes modifiées par la requête DML de lancement. Selon le moment où le déclencheur est déclenché, Aurora PostgreSQL remplit les anciennes et les nouvelles lignes. Par exemple, un
BEFORE INSERTdéclencheur contient uniquement les nouvelles versions des lignes et les anciennes versions vides, car il n'existe aucune ancienne version à référencer.pg_columnmaskne masque pas les tables de transition dans les déclencheurs des tables. Les déclencheurs peuvent utiliser des colonnes masquées à l'intérieur de leur corps et celui-ci voit les données non masquées. Le créateur du déclencheur doit s'assurer de la manière dont le déclencheur est exécuté pour un utilisateur. L'exemple suivant fonctionne correctement dans ce cas.-- Example for table trigger uses masked column in its definition -- Create a table and insert some rows CREATE TABLE public.credit_card_table ( name TEXT, credit_card_no VARCHAR(16), is_fraud BOOL ); INSERT INTO public.credit_card_table (name, credit_card_no, is_fraud) VALUES ('John Doe', '4532015112830366', false), ('Jane Smith', '5410000000000000', true), ('Brad Smith', '1234567891234567', true); -- Create a role which will see masked data and grant it privileges CREATE ROLE intern_user; GRANT SELECT, DELETE ON public.credit_card_table TO intern_user; -- Trigger which will silenty skip delete of non fraudelent credit cards CREATE OR REPLACE FUNCTION prevent_non_fraud_delete() RETURNS TRIGGER AS $$ BEGIN IF OLD.is_fraud = false THEN RETURN NULL; END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER prevent_non_fraud_delete BEFORE DELETE ON credit_card_table FOR EACH ROW EXECUTE FUNCTION prevent_non_fraud_delete(); CREATE OR REPLACE FUNCTION public.return_false() RETURNS BOOLEAN LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT BEGIN ATOMIC SELECT false; END; -- A masking policy that masks both credit card number and is_fraud column. -- If we apply masking inside trigger then prevent_non_fraud_delete trigger will -- allow deleting more rows to masked user (even non fraud ones). CALL pgcolumnmask.create_masking_policy( 'mask_credit_card_no_&_is_fraud'::NAME, 'public.credit_card_table'::REGCLASS, JSON_BUILD_OBJECT('credit_card_no', 'pgcolumnmask.mask_text(credit_card_no)', 'is_fraud', 'public.return_false()')::JSONB, ARRAY['intern_user']::NAME[], 10::INT ); -- Test trigger behaviour using intern_user BEGIN; SET ROLE intern_user; -- credit card number & is_fraud is completely masked from intern_user SELECT * FROM public.credit_card_table; name | credit_card_no | is_fraud ------------+------------------+---------- John Doe | XXXXXXXXXXXXXXXX | f Jane Smith | XXXXXXXXXXXXXXXX | f Brad Smith | XXXXXXXXXXXXXXXX | f (3 rows) -- The delete trigger lets the intern user delete rows for Jane and Brad even though -- intern_user sees their is_fraud = false, but the table trigger works with original -- unmasked value DELETE FROM public.credit_card_table RETURNING *; name | credit_card_no | is_fraud ------------+------------------+---------- Jane Smith | XXXXXXXXXXXXXXXX | f Brad Smith | XXXXXXXXXXXXXXXX | f (2 rows) COMMIT;Le créateur du déclencheur divulgue des données démasquées à l'utilisateur s'il ne fait pas attention aux instructions qu'il utilise dans le corps de son déclencheur. Par exemple, l'utilisation d'un
RAISE NOTICE ‘%’, masked_column;imprime la colonne à l'utilisateur actuel.-- Example showing table trigger leaking column value to current user CREATE OR REPLACE FUNCTION leaky_trigger_func() RETURNS TRIGGER AS $$ BEGIN RAISE NOTICE 'Old credit card number was: %', OLD.credit_card_no; RAISE NOTICE 'New credit card number is %', NEW.credit_card_no; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER leaky_trigger AFTER UPDATE ON public.credit_card_table FOR EACH ROW EXECUTE FUNCTION leaky_trigger_func(); -- Grant update on column is_fraud to auditor role -- auditor will NOT HAVE PERMISSION TO READ DATA CREATE ROLE auditor; GRANT UPDATE (is_fraud) ON public.credit_card_table TO auditor; -- Also add auditor role to existing masking policy on credit card table CALL pgcolumnmask.alter_masking_policy( 'mask_credit_card_no_&_is_fraud'::NAME, 'public.credit_card_table'::REGCLASS, NULL::JSONB, ARRAY['intern_user', 'auditor']::NAME[], NULL::INT ); -- Log in as auditor -- [auditor] -- Update will fail if trying to read data from the table UPDATE public.credit_card_table SET is_fraud = true WHERE credit_card_no = '4532015112830366'; ERROR: permission denied for table cc_table -- [auditor] -- But leaky update trigger will still print the entire row even though -- current user does not have permission to select from public.credit_card_table UPDATE public.credit_card_table SET is_fraud = true; NOTICE: Old credit_card_no was: 4532015112830366 NOTICE: New credit_card_no is 4532015112830366- Déclencheurs sur les vues avec pg_columnmask (au lieu de déclencheurs)
Les déclencheurs ne peuvent être créés que sur des vues dans PostgreSQL. Ils sont utilisés pour exécuter des instructions DML sur des vues qui ne sont pas modifiables. Les tables de transit sont toujours masquées à l'intérieur au lieu d'un déclencheur (IOT), car la vue et les tables de base utilisées dans la requête de vue peuvent avoir des propriétaires différents. Dans ce cas, les tables de base peuvent avoir certaines politiques de masquage applicables au propriétaire de la vue et le propriétaire de la vue doit toujours voir les données masquées des tables de base dans ses déclencheurs. Cela est différent des déclencheurs sur les tables car dans ce cas, le créateur du déclencheur et les données contenues dans les tables appartiennent au même utilisateur, ce qui n'est pas le cas ici.
-- Create a view over credit card table CREATE OR REPLACE VIEW public.credit_card_view AS SELECT * FROM public.credit_card_table; -- Truncate credit card table and insert fresh data TRUNCATE TABLE public.credit_card_table; INSERT INTO public.credit_card_table (name, credit_card_no, is_fraud) VALUES ('John Doe', '4532015112830366', false), ('Jane Smith', '5410000000000000', true), ('Brad Smith', '1234567891234567', true); CREATE OR REPLACE FUNCTION public.print_changes() RETURNS TRIGGER AS $$ BEGIN RAISE NOTICE 'Old row: name=%, credit card number=%, is fraud=%', OLD.name, OLD.credit_card_no, OLD.is_fraud; RAISE NOTICE 'New row: name=%, credit card number=%, is fraud=%', NEW.name, NEW.credit_card_no, NEW.is_fraud; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER print_changes_trigger INSTEAD OF UPDATE ON public.credit_card_view FOR EACH ROW EXECUTE FUNCTION public.print_changes(); GRANT SELECT, UPDATE ON public.credit_card_view TO auditor; -- [auditor] -- Login as auditor role BEGIN; -- Any data coming out from the table will be masked in instead of triggers -- according to masking policies applicable to current user UPDATE public.credit_card_view SET name = CONCAT(name, '_new_name') RETURNING *; NOTICE: Old row: name=John Doe, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: New row: name=John Doe_new_name, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: Old row: name=Jane Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: New row: name=Jane Smith_new_name, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: Old row: name=Brad Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: New row: name=Brad Smith_new_name, credit card number=XXXXXXXXXXXXXXXX, is fraud=f name | credit_card_no | is_fraud ---------------------+------------------+---------- John Doe_new_name | XXXXXXXXXXXXXXXX | f Jane Smith_new_name | XXXXXXXXXXXXXXXX | f Brad Smith_new_name | XXXXXXXXXXXXXXXX | f -- Any new data going into the table using INSERT or UPDATE command will be unmasked UPDATE public.credit_card_view SET credit_card_no = '9876987698769876' RETURNING *; NOTICE: Old row: name=John Doe, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: New row: name=John Doe, credit card number=9876987698769876, is fraud=f NOTICE: Old row: name=Jane Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: New row: name=Jane Smith, credit card number=9876987698769876, is fraud=f NOTICE: Old row: name=Brad Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f NOTICE: New row: name=Brad Smith, credit card number=9876987698769876, is fraud=f name | credit_card_no | is_fraud ------------+------------------+---------- John Doe | 9876987698769876 | f Jane Smith | 9876987698769876 | f Brad Smith | 9876987698769876 | f COMMIT;- Niveau de base de données/utilisateur GuCs pour contrôler le comportement des déclencheurs
Deux paramètres de configuration contrôlent le comportement d'exécution des déclencheurs pour les utilisateurs dotés de politiques de masquage applicables. Utilisez ces paramètres pour empêcher les déclencheurs de s'exécuter sur des tables ou des vues masquées lorsque des restrictions de sécurité supplémentaires sont requises. Les deux paramètres sont désactivés par défaut, ce qui permet aux déclencheurs de s'exécuter normalement.
Premier GUC : restriction de déclenchement sur les tables masquées
Spécifications :
Nom :
pgcolumnmask.restrict_dml_triggers_for_masked_usersType :
booleanPar défaut :
false(les déclencheurs sont autorisés à être exécutés)
Empêche l'exécution du déclencheur sur les tables masquées pour les utilisateurs masqués lorsque ce paramètre est défini sur TRUE.
pg_columnmaskcorrige l'erreur.Deuxième GUC : restriction du déclenchement du déclenchement sur les vues avec des tables masquées
Spécifications :
Nom :
pgcolumnmask.restrict_iot_triggers_for_masked_usersType :
booleanPar défaut :
false(les déclencheurs sont autorisés à être exécutés)
Empêche l'exécution de déclencheurs sur les vues qui incluent des tables masquées dans leur définition pour les utilisateurs masqués lorsqu'elles sont définies sur TRUE.
Ces paramètres fonctionnent indépendamment et sont configurables comme les paramètres de configuration de base de données standard.