PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Anonimisasi PostgreSQL Sesuai Permintaan

Sebelum, selama, dan setelah GDPR hadir di tahun 2018, ada banyak ide untuk memecahkan masalah penghapusan atau penyembunyian data pengguna, menggunakan berbagai lapisan tumpukan perangkat lunak tetapi juga menggunakan berbagai pendekatan (penghapusan keras, penghapusan lunak, anonimisasi). Anonimisasi adalah salah satunya yang dikenal populer di kalangan organisasi/perusahaan berbasis PostgreSQL.

Dalam semangat GDPR, kami melihat semakin banyak persyaratan untuk dokumen bisnis dan laporan yang dipertukarkan antar perusahaan, sehingga individu yang ditampilkan dalam laporan tersebut disajikan secara anonim, yaitu hanya peran/jabatan mereka yang ditampilkan , sementara data pribadi mereka disembunyikan. Hal ini terjadi kemungkinan besar karena fakta bahwa perusahaan yang menerima laporan ini tidak ingin mengelola data ini di bawah prosedur/proses GDPR, mereka tidak ingin berurusan dengan beban merancang prosedur/proses/sistem baru untuk menanganinya , dan mereka hanya meminta untuk menerima data yang sudah dianonimkan sebelumnya. Jadi anonimisasi ini tidak hanya berlaku untuk individu yang telah menyatakan keinginannya untuk dilupakan, tetapi sebenarnya semua orang yang disebutkan di dalam laporan, yang sangat berbeda dari praktik GDPR pada umumnya.

Dalam artikel ini, kita akan membahas anonimisasi menuju solusi untuk masalah ini. Kami akan mulai dengan menghadirkan solusi permanen, yaitu solusi di mana seseorang yang meminta untuk dilupakan harus disembunyikan di semua pertanyaan di masa mendatang dalam sistem. Kemudian membangun di atas ini kami akan menyajikan cara untuk mencapai "sesuai permintaan" yaitu anonimisasi berumur pendek, yang berarti penerapan mekanisme anonimisasi dimaksudkan untuk aktif cukup lama sampai laporan yang dibutuhkan dihasilkan dalam sistem. Dalam solusi yang saya sajikan ini akan memiliki efek global, jadi solusi ini menggunakan pendekatan serakah, mencakup semua aplikasi, dengan penulisan ulang kode minimal (jika ada) (dan berasal dari kecenderungan DBA PostgreSQL untuk menyelesaikan masalah seperti itu secara terpusat meninggalkan aplikasi pengembang berurusan dengan beban kerja mereka yang sebenarnya). Namun, metode yang disajikan di sini dapat dengan mudah diubah untuk diterapkan dalam cakupan terbatas/sempit.

Anonimisasi Permanen

Di sini kami akan menyajikan cara untuk mencapai anonimisasi. Mari kita perhatikan tabel berikut yang berisi catatan karyawan perusahaan:

testdb=# create table person(id serial primary key, surname text not null, givenname text not null, midname text, address text not null, email text not null, role text not null, rank text not null);
CREATE TABLE
testdb=# insert into person(surname,givenname,address,email,role,rank) values('Singh','Kumar','2 some street, Mumbai, India','[email protected]','Seafarer','Captain');
INSERT 0 1
testdb=# insert into person(surname,givenname,address,email,role,rank) values('Mantzios','Achilleas','Agiou Titou 10, Iraklio, Crete, Greece','[email protected]','IT','DBA');
INSERT 0 1
testdb=# insert into person(surname,givenname,address,email,role,rank) values('Emanuel','Tsatsadakis','Knossou 300, Iraklio, Crete, Greece','[email protected]','IT','Developer');
INSERT 0 1
testdb=#

Tabel ini bersifat publik, semua orang dapat menanyakannya, dan termasuk dalam skema publik. Sekarang kita membuat mekanisme dasar untuk anonimisasi yang terdiri dari:

  • skema baru untuk menampung tabel dan tampilan terkait, sebut saja ini anonim
  • tabel yang berisi id orang yang ingin dilupakan:anonym.person_anonym
  • tampilan yang menyediakan versi anonim dari public.person:anonym.person
  • pengaturan jalur_pencarian, untuk menggunakan tampilan baru
testdb=# create schema anonym;
CREATE SCHEMA
testdb=# create table anonym.person_anonym(id INT NOT NULL REFERENCES public.person(id));
CREATE TABLE
CREATE OR REPLACE VIEW anonym.person AS
SELECT p.id,
    CASE
        WHEN pa.id IS NULL THEN p.givenname
        ELSE '****'::character varying
    END AS givenname,
    CASE
        WHEN pa.id IS NULL THEN p.midname
        ELSE '****'::character varying
    END AS midname,
    CASE
        WHEN pa.id IS NULL THEN p.surname
        ELSE '****'::character varying
    END AS surname,
    CASE
        WHEN pa.id IS NULL THEN p.address
        ELSE '****'::text
    END AS address,
    CASE
        WHEN pa.id IS NULL THEN p.email
        ELSE '****'::character varying
    END AS email,
    role,
    rank
  FROM person p
LEFT JOIN anonym.person_anonym pa ON p.id = pa.id
;

Mari kita atur search_path ke aplikasi kita:

set search_path = anonym,"$user", public;

Peringatan :sangat penting bahwa search_path diatur dengan benar dalam definisi sumber data dalam aplikasi. Pembaca didorong untuk mengeksplorasi cara yang lebih maju untuk menangani jalur pencarian, mis. dengan penggunaan fungsi yang dapat menangani logika yang lebih kompleks dan dinamis. Misalnya, Anda dapat menentukan satu set pengguna entri data (atau peran), dan membiarkan mereka tetap menggunakan tabel public.person sepanjang interval anonimisasi (sehingga mereka akan terus melihat data normal), sambil mendefinisikan set pengguna manajerial/pelaporan (atau peran) untuk siapa logika anonimisasi akan diterapkan.

Sekarang mari kita query relasi person kita:

testdb=# select * from person;
-[ RECORD 1 ]-------------------------------------
id    | 2
givenname | Achilleas
midname   |
surname   | Mantzios
address   | Agiou Titou 10, Iraklio, Crete, Greece
email | [email protected]
role  | IT
rank  | DBA
-[ RECORD 2 ]-------------------------------------
id    | 1
givenname | Kumar
midname   |
surname   | Singh
address   | 2 some street, Mumbai, India
email | [email protected]
role  | Seafarer
rank  | Captain
-[ RECORD 3 ]-------------------------------------
id    | 3
givenname | Tsatsadakis
midname   |
surname   | Emanuel
address   | Knossou 300, Iraklio, Crete, Greece
email | [email protected]
role  | IT
rank  | Developer

testdb=#

Sekarang, anggaplah Tuan Singh meninggalkan perusahaan dan secara eksplisit menyatakan haknya untuk dilupakan dengan pernyataan tertulis. Aplikasi melakukan ini dengan memasukkan id-nya ke dalam set id "untuk dilupakan":

testdb=# insert into anonym.person_anonym (id) VALUES(1);
INSERT 0 1

Sekarang mari kita ulangi kueri persis seperti yang kita jalankan sebelumnya:

testdb=# select * from person;
-[ RECORD 1 ]-------------------------------------
id    | 1
givenname | ****
midname   | ****
surname   | ****
address   | ****
email | ****
role  | Seafarer
rank  | Captain
-[ RECORD 2 ]-------------------------------------
id    | 2
givenname | Achilleas
midname   |
surname   | Mantzios
address   | Agiou Titou 10, Iraklio, Crete, Greece
email | [email protected]
role  | IT
rank  | DBA
-[ RECORD 3 ]-------------------------------------
id    | 3
givenname | Tsatsadakis
midname   |
surname   | Emanuel
address   | Knossou 300, Iraklio, Crete, Greece
email | [email protected]
role  | IT
rank  | Developer

testdb=#

Kami dapat melihat bahwa detail Mr Singh tidak dapat diakses dari aplikasi.

Anonimisasi Global Sementara

Ide Utama

  • Pengguna menandai awal dari interval anonimisasi (waktu yang singkat).
  • Selama interval ini, hanya pilihan yang diperbolehkan untuk tabel bernama orang.
  • Semua akses (pilihan) dianonimkan untuk semua catatan di tabel orang, terlepas dari penyiapan anonimisasi sebelumnya.
  • Pengguna menandai akhir dari interval anonimisasi.

Blok Bangunan

  • Komit dua fase (alias Transaksi yang Disiapkan).
  • Penguncian tabel eksplisit.
  • Penyiapan anonimisasi yang kami lakukan di atas di bagian “Anonimisasi Permanen”.

Implementasi

Aplikasi admin khusus (misalnya bernama :markStartOfAnynimizationPeriod) bekerja 

testdb=# BEGIN ;
BEGIN
testdb=# LOCK public.person IN SHARE MODE ;
LOCK TABLE
testdb=# PREPARE TRANSACTION 'personlock';
PREPARE TRANSACTION
testdb=#

Apa yang dilakukan di atas adalah memperoleh kunci pada tabel dalam mode SHARE sehingga INSERTS, UPDATES, DELETES diblokir. Juga dengan memulai transaksi komit dua fase (transaksi yang disiapkan AKA, dalam konteks lain yang dikenal sebagai transaksi terdistribusi atau transaksi Arsitektur eXtended XA) kami membebaskan transaksi dari koneksi sesi yang menandai awal periode anonimisasi, sementara membiarkan sesi berikutnya lainnya dilakukan menyadari keberadaannya. Transaksi yang disiapkan adalah transaksi persisten yang tetap hidup setelah pemutusan koneksi/sesi yang memulainya (melalui SIAPKAN TRANSAKSI). Perhatikan bahwa pernyataan "SIAPKAN TRANSAKSI" memisahkan transaksi dari sesi saat ini. Transaksi yang disiapkan dapat diambil oleh sesi berikutnya dan dibatalkan atau dikomit. Penggunaan jenis transaksi XA ini memungkinkan sistem untuk menangani banyak sumber data XA yang berbeda secara andal, dan melakukan logika transaksional di seluruh sumber data tersebut (mungkin heterogen). Namun, alasan kami menggunakannya dalam kasus khusus ini :

  • untuk mengaktifkan sesi klien yang mengeluarkan untuk mengakhiri sesi dan memutuskan/membebaskan koneksinya (meninggalkan atau bahkan lebih buruk "bertahan" koneksi adalah ide yang sangat buruk, koneksi harus dibebaskan segera setelah berfungsi pertanyaan yang perlu dilakukan)
  • untuk membuat sesi/koneksi berikutnya mampu menanyakan keberadaan transaksi yang disiapkan ini
  • untuk membuat sesi akhir mampu melakukan transaksi yang disiapkan ini (dengan menggunakan namanya) sehingga menandai :
    • pelepasan kunci SHARE MODE
    • akhir periode anonimisasi

Untuk memverifikasi bahwa transaksi itu hidup dan terkait dengan kunci SHARE pada tabel person kami, kami melakukan:

testdb=# select px.*,l0.* from pg_prepared_xacts px , pg_locks l0 where px.gid='personlock' AND l0.virtualtransaction='-1/'||px.transaction AND l0.relation='public.person'::regclass AND l0.mode='ShareLock';
-[ RECORD 1 ]------+----------------------------
transaction    | 725
gid            | personlock
prepared       | 2020-05-23 15:34:47.2155+03
owner          | postgres
database       | testdb
locktype       | relation
database       | 16384
relation       | 32829
page           |
tuple          |
virtualxid     |
transactionid  |
classid        |
objid          |
objsubid       |
virtualtransaction | -1/725
pid            |
mode           | ShareLock
granted        | t
fastpath       | f

testdb=#

Yang dilakukan kueri di atas adalah memastikan bahwa personlock transaksi yang disiapkan bernama itu hidup dan bahwa memang kunci terkait pada tabel person yang dipegang oleh transaksi virtual ini dalam mode yang dimaksud :SHARE.

Jadi sekarang kita dapat mengubah tampilan:

CREATE OR REPLACE VIEW anonym.person AS
WITH perlockqry AS (
    SELECT 1
      FROM pg_prepared_xacts px,
        pg_locks l0
      WHERE px.gid = 'personlock'::text AND l0.virtualtransaction = ('-1/'::text || px.transaction) AND l0.relation = 'public.person'::regclass::oid AND l0.mode = 'ShareLock'::text
    )
SELECT p.id,
    CASE
        WHEN pa.id IS NULL AND NOT (EXISTS ( SELECT 1
          FROM perlockqry)) THEN p.givenname::character varying
        ELSE '****'::character varying
    END AS givenname,
    CASE
        WHEN pa.id IS NULL AND NOT (EXISTS ( SELECT 1
          FROM perlockqry)) THEN p.midname::character varying
        ELSE '****'::character varying
    END AS midname,
    CASE
        WHEN pa.id IS NULL AND NOT (EXISTS ( SELECT 1
          FROM perlockqry)) THEN p.surname::character varying
        ELSE '****'::character varying
    END AS surname,
    CASE
        WHEN pa.id IS NULL AND NOT (EXISTS ( SELECT 1
          FROM perlockqry)) THEN p.address
        ELSE '****'::text
    END AS address,
    CASE
        WHEN pa.id IS NULL AND NOT (EXISTS ( SELECT 1
          FROM perlockqry)) THEN p.email::character varying
        ELSE '****'::character varying
    END AS email,
p.role,
p.rank
  FROM public.person p
LEFT JOIN person_anonym pa ON p.id = pa.id

Sekarang dengan definisi baru, jika pengguna telah memulai transaksi personlock, maka pilihan berikut akan kembali:

testdb=# select * from person;
id | givenname | midname | surname | address | email |   role   |   rank   
----+-----------+---------+---------+---------+-------+----------+-----------
  1 | ****  | **** | **** | **** | ****  | Seafarer | Captain
  2 | ****  | **** | **** | **** | ****  | IT   | DBA
  3 | ****  | **** | **** | **** | ****  | IT   | Developer
(3 rows)

testdb=#

yang berarti anonimisasi tanpa syarat global.

Aplikasi apa pun yang mencoba menggunakan data dari table person akan mendapatkan “****” yang dianonimkan alih-alih data nyata yang sebenarnya. Sekarang anggaplah admin aplikasi ini memutuskan periode anonimisasi akan berakhir, jadi aplikasinya sekarang mengeluarkan:

COMMIT PREPARED 'personlock';

Sekarang setiap pilihan berikutnya akan kembali:

testdb=# select * from person;
id |  givenname  | midname | surname  |            address             |         email         |   role   |   rank   
----+-------------+---------+----------+----------------------------------------+-------------------------------+----------+-----------
  1 | ****    | **** | **** | ****                               | ****                      | Seafarer | Captain
  2 | Achilleas   |     | Mantzios | Agiou Titou 10, Iraklio, Crete, Greece | [email protected]   | IT   | DBA
  3 | Tsatsadakis |     | Emanuel  | Knossou 300, Iraklio, Crete, Greece | [email protected] | IT   | Developer
(3 rows)

testdb=#

Peringatan! :Kunci mencegah penulisan bersamaan, tetapi tidak mencegah penulisan akhirnya saat kunci akan dilepaskan. Jadi ada potensi bahaya untuk memperbarui aplikasi, membaca '****' dari database, pengguna yang ceroboh, menekan pembaruan, dan kemudian setelah beberapa waktu menunggu, kunci SHARED dilepaskan dan pembaruan berhasil menulis '*** *' sebagai pengganti data normal yang benar. Pengguna tentu saja dapat membantu di sini dengan tidak menekan tombol secara membabi buta, tetapi beberapa perlindungan tambahan dapat ditambahkan di sini. Memperbarui aplikasi dapat menimbulkan:

set lock_timeout TO 1;

di awal  transaksi pembaruan. Dengan cara ini, setiap menunggu/memblokir lebih dari 1 md akan menimbulkan pengecualian. Yang seharusnya melindungi dari sebagian besar kasus. Cara lain adalah dengan memeriksa batasan di salah satu bidang sensitif untuk memeriksa nilai '****'.

ALARM! :sangat penting bahwa transaksi yang disiapkan pada akhirnya harus diselesaikan. Baik oleh pengguna yang memulainya (atau pengguna lain), atau bahkan oleh skrip cron yang memeriksa transaksi yang terlupakan setiap katakanlah 30 menit. Lupa untuk mengakhiri transaksi ini akan menyebabkan hasil yang fatal karena mencegah VACUUM berjalan, dan tentu saja kunci akan tetap ada, mencegah penulisan ke database. Jika Anda tidak cukup nyaman dengan sistem Anda, jika Anda tidak sepenuhnya memahami semua aspek dan semua efek samping dari menggunakan transaksi yang disiapkan/didistribusikan dengan kunci, jika Anda tidak memiliki pemantauan yang memadai, terutama mengenai MVCC metrik, maka jangan ikuti pendekatan ini. Dalam hal ini, Anda dapat memiliki parameter penyimpanan tabel khusus untuk tujuan admin di mana Anda dapat menggunakan dua nilai kolom khusus, satu untuk operasi normal dan satu untuk anonimisasi yang dipaksakan secara global, atau Anda dapat bereksperimen dengan kunci saran bersama tingkat aplikasi PostgreSQL:

  • https://www.postgresql.org/docs/10/explicit-locking.html#ADVISORY-LOCKS
  • https://www.postgresql.org/docs/10/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Praktik Terbaik Keamanan PostgreSQL

  2. Operasi CRUD Generik 2.0 yang licin

  3. Mode postgresql H2 sepertinya tidak berfungsi untuk saya

  4. Tidak dapat menggunakan tabel bernama pengguna di postgresql hibernate

  5. Paket PGLogical 1.1 untuk PostgreSQL 9.6beta1