Bewährte Methoden für die sichere Implementierung von pg_columnmask - Amazon Aurora

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Bewährte Methoden für die sichere Implementierung von pg_columnmask

Der folgende Abschnitt enthält bewährte Sicherheitsmethoden für die Implementierung pg_columnmask in Ihrer Aurora PostgreSQL-Umgebung. Folgen Sie diesen Empfehlungen, um:

  • Richten Sie eine sichere, rollenbasierte Architektur für die Zugriffskontrolle ein

  • Entwickeln Sie Maskierungsfunktionen, die Sicherheitslücken verhindern

  • Verstehen und kontrollieren Sie das Triggerverhalten mit maskierten Daten

Rollenbasierte Sicherheitsarchitektur

Definieren Sie eine Rollenhierarchie, um Zugriffskontrollen in Ihrer Datenbank zu implementieren. Aurora PostgreSQL pg_columnmask erweitert diese Kontrollen, indem es eine zusätzliche Ebene für die feinkörnige Datenmaskierung innerhalb dieser Rollen bereitstellt.

Erstellen Sie spezielle Rollen, die auf die Unternehmensfunktionen abgestimmt sind, anstatt einzelnen Benutzern Berechtigungen zu gewähren. Dieser Ansatz bietet eine bessere Überprüfbarkeit und vereinfacht das Berechtigungsmanagement, wenn sich Ihre Organisationsstruktur weiterentwickelt.

Beispiel der Schaffung einer organisatorischen Rollenhierarchie

Im folgenden Beispiel wird eine organisatorische Rollenhierarchie mit speziellen Rollen für verschiedene Funktionen erstellt und anschließend einzelnen Benutzern die entsprechenden Rollen zugewiesen. In diesem Beispiel werden zuerst Organisationsrollen (analyst_role, support_role) erstellt, dann wird einzelnen Benutzern die Mitgliedschaft in diesen Rollen gewährt. Diese Struktur ermöglicht es Ihnen, Berechtigungen auf Rollenebene und nicht für jeden einzelnen Benutzer zu verwalten.

-- 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;

Implementieren Sie das Prinzip der geringsten Rechte, indem Sie nur die für jede Rolle erforderlichen Mindestberechtigungen gewähren. Vermeiden Sie die Gewährung umfassender Berechtigungen, die ausgenutzt werden könnten, wenn Anmeldeinformationen kompromittiert werden.

-- 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;

Richtlinienadministratoren benötigen USAGE Rechte für Schemas, in denen sie Maskierungsrichtlinien verwalten. Gewähren Sie diese Rechte selektiv und folgen Sie dabei dem Prinzip der geringsten Rechte. Führen Sie regelmäßige Überprüfungen der Schemazugriffsberechtigungen durch, um sicherzustellen, dass nur autorisiertes Personal über die Funktionen zur Richtlinienverwaltung verfügt.

Die Konfiguration der Rollenparameter des Policy-Administrators ist nur auf Datenbankadministratoren beschränkt. Dieser Parameter kann nicht auf Datenbank- oder Sitzungsebene geändert werden, wodurch verhindert wird, dass Benutzer ohne Benutzerrechte die Zuweisungen von Richtlinienadministratoren außer Kraft setzen. Diese Einschränkung stellt sicher, dass die Kontrolle der Maskierungsrichtlinien zentralisiert und sicher bleibt.

Weisen Sie die Rolle des Richtlinienadministrators bestimmten Personen und nicht Gruppen zu. Dieser gezielte Ansatz gewährleistet einen selektiven Zugriff auf die Verwaltung von Maskierungsrichtlinien, da Richtlinienadministratoren alle Tabellen in der Datenbank maskieren können.

Entwicklung sicherer Maskierungsfunktionen

Entwickeln Sie Maskierungsfunktionen unter Verwendung von Semantik für frühe Bindungen, um eine korrekte Nachverfolgung von Abhängigkeiten sicherzustellen und Sicherheitslücken bei späten Bindungen zu vermeiden, z. B. die Änderung von Suchpfaden während der Laufzeit. Es wird empfohlen, die BEGIN ATOMIC Syntax für SQL-Funktionen zu verwenden, um die Validierung während der Kompilierung (d. h. frühes Binden) und das Abhängigkeitsmanagement zu ermöglichen.

-- 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;

Alternativ können Sie Funktionen erstellen, die immun gegen Änderungen des Suchpfads sind, indem Sie alle Objektreferenzen explizit per Schema qualifizieren und so ein konsistentes Verhalten über verschiedene Benutzersitzungen hinweg sicherstellen.

-- 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; $$;

Implementieren Sie die Eingabevalidierung innerhalb von Maskierungsfunktionen, um Sonderfälle zu behandeln und unerwartetes Verhalten zu verhindern. Schließen Sie immer die NULL-Behandlung ein und validieren Sie die Eingabeformate, um ein konsistentes Maskierungsverhalten sicherzustellen.

-- 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;

DML löst Verhalten mit pg_columnmask aus

Bei Tabellentriggern werden Übergangstabellen vollständig demaskiert. Bei View-Triggern (IOT) werden Übergangstabellen entsprechend den Anzeigeberechtigungen des aktuellen Benutzers maskiert.

Tabellentrigger werden mit pg_columnmask ausgelöst

Triggern wird eine Übergangstabelle übergeben, die die alte und die neue Version der Zeilen enthält, die durch die auslösende DML-Abfrage geändert wurden. Je nachdem, wann der Trigger ausgelöst wird, füllt Aurora PostgreSQL die alten und neuen Zeilen auf. Ein BEFORE INSERT Trigger hat beispielsweise nur neue Versionen der Zeilen und leere alte Versionen, weil es keine alte Version gibt, auf die verwiesen werden kann.

pg_columnmaskmaskiert keine Übergangstabellen innerhalb von Triggern für Tabellen. Trigger können maskierte Spalten in ihrem Hauptteil verwenden, und es werden unmaskierte Daten angezeigt. Der Ersteller des Triggers sollte sicherstellen, wie der Trigger für einen Benutzer ausgeführt wird. Das folgende Beispiel funktioniert in diesem Fall korrekt.

-- 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;

Der Trigger-Ersteller gibt unmaskierte Daten an den Benutzer weiter, wenn er nicht vorsichtig mit den Anweisungen umgeht, die er in seinem Triggertext verwendet. Wenn Sie beispielsweise a verwenden, wird RAISE NOTICE ‘%’, masked_column; die Spalte an den aktuellen Benutzer ausgegeben.

-- 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
Wird bei Ansichten mit pg_columnmask ausgelöst (anstelle von Triggern)

Trigger können nur für Ansichten in PostgreSQL erstellt werden. Sie werden für die Ausführung von DML-Anweisungen in Ansichten verwendet, die nicht aktualisierbar sind. Transittabellen werden immer intern maskiert und nicht innerhalb von Triggern (IOT), da die in der View-Abfrage verwendete View und die Basistabellen unterschiedliche Besitzer haben können. In diesem Fall können für Basistabellen einige Maskierungsrichtlinien gelten, die für den Eigentümer der Ansicht gelten, und der Eigentümer der Ansicht muss in seinen Triggern immer maskierte Daten aus Basistabellen sehen. Dies unterscheidet sich von Triggern für Tabellen, da in diesem Fall der Trigger-Ersteller und die Daten in den Tabellen demselben Benutzer gehören, was hier nicht der Fall ist.

-- 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;
Datenbank-/Benutzerebene zur Steuerung des GuCs Triggerverhaltens

Zwei Konfigurationsparameter steuern das Trigger-Ausführungsverhalten für Benutzer mit entsprechenden Maskierungsrichtlinien. Verwenden Sie diese Parameter, um zu verhindern, dass Trigger in maskierten Tabellen oder Ansichten ausgeführt werden, wenn zusätzliche Sicherheitseinschränkungen erforderlich sind. Beide Parameter sind standardmäßig deaktiviert, sodass Trigger normal ausgeführt werden können.

Erster GUC: Beschränkung der Triggerauslösung für maskierte Tabellen

Spezifikationen:

  • Name: pgcolumnmask.restrict_dml_triggers_for_masked_users

  • Typ: boolean

  • Standard: false (Trigger dürfen ausgeführt werden)

Wenn dieser Wert auf TRUE gesetzt ist, wird die Ausführung von Triggern in maskierten Tabellen für maskierte Benutzer verhindert. pg_columnmaskdurchläuft den Fehler.

Zweiter GUC: Einschränkung beim Auslösen von Triggern für Ansichten mit maskierten Tabellen

Spezifikationen:

  • Name: pgcolumnmask.restrict_iot_triggers_for_masked_users

  • Typ: boolean

  • Standard: false (Trigger dürfen ausgeführt werden)

Wenn der Wert auf TRUE gesetzt ist, wird die Ausführung von Triggern in Ansichten verhindert, deren Definition maskierte Tabellen für maskierte Benutzer enthält.

Diese Parameter funktionieren unabhängig voneinander und sind wie standardmäßige Datenbankkonfigurationsparameter konfigurierbar.