Praktik terbaik untuk implementasi pg_columnmask yang aman - Amazon Aurora

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Praktik terbaik untuk implementasi pg_columnmask yang aman

Bagian berikut menyediakan praktik terbaik keamanan untuk diterapkan pg_columnmask di lingkungan Aurora PostgreSQL Anda. Ikuti rekomendasi ini untuk:

  • Membangun arsitektur kontrol akses berbasis peran yang aman

  • Kembangkan fungsi masking yang mencegah kerentanan keamanan

  • Memahami dan mengontrol perilaku pemicu dengan data bertopeng

Arsitektur keamanan berbasis peran

Tentukan hierarki peran untuk mengimplementasikan kontrol akses dalam database Anda. Aurora PostgreSQL pg_columnmask menambah kontrol ini dengan menyediakan lapisan tambahan untuk penyembunyian data berbutir halus dalam peran tersebut.

Buat peran khusus yang selaras dengan fungsi organisasi daripada memberikan izin kepada pengguna individu. Pendekatan ini memberikan auditabilitas yang lebih baik dan menyederhanakan manajemen izin saat struktur organisasi Anda berkembang.

contoh Menciptakan Heirarki Peran Organisasi

Contoh berikut membuat hierarki peran organisasi dengan peran khusus untuk fungsi yang berbeda, kemudian menetapkan pengguna individu ke peran yang sesuai. Dalam contoh ini, peran organisasi (analyst_role, support_role) dibuat terlebih dahulu, kemudian pengguna individu diberikan keanggotaan dalam peran ini. Struktur ini memungkinkan Anda mengelola izin di tingkat peran, bukan untuk setiap pengguna individu.

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

Menerapkan prinsip hak istimewa terkecil dengan hanya memberikan izin minimum yang diperlukan untuk setiap peran. Hindari pemberian izin luas yang dapat dieksploitasi jika kredensyal dikompromikan.

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

Administrator kebijakan memerlukan USAGE hak istimewa pada skema tempat mereka mengelola kebijakan penyembunyian. Berikan hak istimewa ini secara selektif, mengikuti prinsip hak istimewa paling sedikit. Lakukan tinjauan reguler atas izin akses skema untuk memastikan hanya personel yang berwenang yang mempertahankan kemampuan manajemen kebijakan.

Konfigurasi parameter peran admin kebijakan dibatasi hanya untuk administrator database. Parameter ini tidak dapat diubah pada tingkat basis data atau sesi, mencegah pengguna yang tidak memiliki hak istimewa untuk mengganti penetapan admin kebijakan. Pembatasan ini memastikan bahwa kontrol kebijakan masking tetap terpusat dan aman.

Tetapkan peran admin kebijakan untuk individu tertentu, bukan grup. Pendekatan yang ditargetkan ini memastikan akses selektif ke manajemen kebijakan masking, karena administrator kebijakan memiliki kemampuan untuk menutupi semua tabel dalam database.

Pengembangan fungsi masking yang aman

Kembangkan fungsi masking menggunakan semantik pengikatan awal untuk memastikan pelacakan ketergantungan yang tepat dan mencegah kerentanan pengikatan yang terlambat seperti modifikasi jalur pencarian selama runtime. Disarankan untuk menggunakan BEGIN ATOMIC sintaks untuk fungsi SQL untuk mengaktifkan validasi waktu kompilasi (yaitu pengikatan awal) dan manajemen ketergantungan.

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

Atau, buat fungsi yang kebal terhadap perubahan jalur pencarian dengan skema eksplisit yang memenuhi syarat semua referensi objek, memastikan perilaku yang konsisten di seluruh sesi pengguna yang berbeda.

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

Terapkan validasi input dalam fungsi masking untuk menangani kasus tepi dan mencegah perilaku yang tidak terduga. Selalu sertakan penanganan NULL dan validasi format input untuk memastikan perilaku masking yang konsisten.

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

DMLMemicu perilaku dengan pg_columnmask

Untuk pemicu tabel, tabel transisi akan sepenuhnya dibuka kedoknya. Untuk pemicu tampilan (IOT), tabel transisi akan disamarkan sesuai dengan izin tampilan pengguna saat ini.

Pemicu tabel dengan pg_columnmask

Pemicu dilewatkan tabel transisi yang berisi versi lama dan baru dari baris yang dimodifikasi oleh query DMLnya. Tergantung pada kapan pemicu diaktifkan, Aurora PostgreSQL mengisi baris lama dan baru. Misalnya, BEFORE INSERT pemicu hanya memiliki versi baru dari baris dan versi lama kosong karena tidak ada versi lama untuk dirujuk.

pg_columnmasktidak menutupi tabel transisi di dalam pemicu pada tabel. Pemicu dapat menggunakan kolom bertopeng di dalam tubuh mereka dan melihat data yang dibuka kedoknya. Pembuat pemicu harus memastikan bagaimana pemicu dieksekusi untuk pengguna. Contoh berikut berfungsi dengan benar dalam kasus ini.

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

Pembuat pemicu membocorkan data yang dibuka kedok ke pengguna jika mereka tidak berhati-hati dengan pernyataan yang mereka gunakan di badan pemicu mereka. Misalnya menggunakan RAISE NOTICE ‘%’, masked_column; cetakan kolom untuk pengguna saat ini.

-- 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
Pemicu tampilan dengan pg_columnmask (Alih-alih pemicu)

Pemicu hanya dapat dibuat pada tampilan di PostgreSQL. Mereka digunakan untuk menjalankan pernyataan DHTML pada tampilan yang tidak dapat diperbarui. Tabel transit selalu tertutup di dalam bukan pemicu (IOT), karena tampilan dan tabel dasar yang digunakan di dalam kueri tampilan dapat memiliki pemilik yang berbeda. Dalam hal ini, tabel dasar mungkin memiliki beberapa kebijakan masking yang berlaku pada pemilik tampilan dan pemilik tampilan harus selalu melihat data bertopeng dari tabel dasar di dalam pemicunya. Ini berbeda dari pemicu pada tabel karena dalam hal ini pembuat pemicu dan data di dalam tabel dimiliki oleh pengguna yang sama yang tidak terjadi di sini.

-- 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;
Database/Tingkat pengguna GuCs untuk mengontrol perilaku pemicu

Dua parameter konfigurasi mengontrol perilaku eksekusi pemicu bagi pengguna dengan kebijakan masking yang berlaku. Gunakan parameter ini untuk mencegah pemicu mengeksekusi pada tabel atau tampilan bertopeng saat pembatasan keamanan tambahan diperlukan. Kedua parameter dinonaktifkan secara default, memungkinkan pemicu untuk dijalankan secara normal.

GUC Pertama: Memicu pembatasan penembakan pada tabel bertopeng

Spesifikasi:

  • Nama: pgcolumnmask.restrict_dml_triggers_for_masked_users

  • Tipe: boolean

  • Default: false (pemicu diizinkan untuk dieksekusi)

Mencegah eksekusi pemicu pada tabel bertopeng untuk pengguna bertopeng saat disetel ke TRUE. pg_columnmaskberjalan melalui kesalahan.

GUC Kedua: Memicu pembatasan penembakan pada tampilan dengan tabel bertopeng

Spesifikasi:

  • Nama: pgcolumnmask.restrict_iot_triggers_for_masked_users

  • Tipe: boolean

  • Default: false (pemicu diizinkan untuk dieksekusi)

Mencegah eksekusi pemicu pada tampilan yang menyertakan tabel bertopeng dalam definisinya untuk pengguna bertopeng saat disetel ke TRUE.

Parameter ini beroperasi secara independen dan dapat dikonfigurasi seperti parameter konfigurasi basis data standar.