Trước, trong và sau khi GDPR có hiệu lực vào năm 2018, đã có nhiều ý tưởng để giải quyết vấn đề xóa hoặc ẩn dữ liệu người dùng, sử dụng nhiều lớp khác nhau của ngăn xếp phần mềm nhưng cũng sử dụng nhiều phương pháp khác nhau (xóa cứng, xóa mềm, ẩn danh). Ẩn danh là một trong số chúng được biết đến là phổ biến trong các tổ chức / công ty dựa trên PostgreSQL.
Theo tinh thần của GDPR, chúng tôi nhận thấy ngày càng có nhiều yêu cầu đối với các tài liệu kinh doanh và báo cáo được trao đổi giữa các công ty, do đó các cá nhân được hiển thị trong các báo cáo đó được hiển thị ẩn danh, tức là chỉ có vai trò / chức danh của họ được hiển thị , trong khi dữ liệu cá nhân của họ bị ẩn. Điều này xảy ra nhiều nhất có lẽ là do những công ty nhận được các báo cáo này không muốn quản lý những dữ liệu này theo quy trình / quy trình của GDPR, họ không muốn đối phó với gánh nặng thiết kế các quy trình / quy trình / hệ thống mới để xử lý chúng. và họ chỉ yêu cầu nhận dữ liệu đã được ẩn danh trước. Vì vậy, việc ẩn danh này không chỉ áp dụng cho những cá nhân đã bày tỏ mong muốn được lãng quên, mà trên thực tế tất cả những người được đề cập trong báo cáo, điều này hoàn toàn khác với các thông lệ GDPR phổ biến.
Trong bài viết này, chúng ta sẽ giải quyết vấn đề ẩn danh để hướng tới giải pháp cho vấn đề này. Chúng tôi sẽ bắt đầu với việc trình bày một giải pháp vĩnh viễn, đó là một giải pháp mà một người yêu cầu được quên nên được ẩn trong tất cả các yêu cầu trong tương lai trong hệ thống. Sau đó, dựa trên điều này, chúng tôi sẽ trình bày một cách để đạt được "theo yêu cầu", tức là ẩn danh trong thời gian ngắn, có nghĩa là triển khai cơ chế ẩn danh dự định chỉ hoạt động đủ lâu cho đến khi các báo cáo cần thiết được tạo trong hệ thống. Trong giải pháp mà tôi đang trình bày, điều này sẽ có hiệu ứng toàn cầu, vì vậy giải pháp này sử dụng cách tiếp cận tham lam, bao gồm tất cả các ứng dụng, với việc viết lại mã tối thiểu (nếu có) (và xuất phát từ xu hướng của PostgreSQL DBA là giải quyết các vấn đề như vậy một cách tập trung khi rời khỏi ứng dụng các nhà phát triển giải quyết khối lượng công việc thực sự của họ). Tuy nhiên, các phương pháp được trình bày ở đây có thể dễ dàng điều chỉnh để áp dụng trong phạm vi hạn chế / hẹp hơn.
Ẩn danh vĩnh viễn
Ở đây chúng tôi sẽ trình bày một cách để đạt được ẩn danh. Hãy xem xét bảng sau chứa hồ sơ về nhân viên của công ty:
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=#
Bảng này là công khai, mọi người đều có thể truy vấn nó và thuộc về lược đồ công khai. Bây giờ chúng tôi tạo cơ chế cơ bản để ẩn danh bao gồm:
- một lược đồ mới để chứa các bảng và chế độ xem có liên quan, hãy gọi đây là chế độ ẩn danh
- một bảng chứa id của những người muốn bị lãng quên:anonym.man_anonym
- một chế độ xem cung cấp phiên bản ẩn danh của public.woman:anonym. person
- thiết lập search_path, để sử dụng chế độ xem mới
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
;
Hãy đặt search_path thành ứng dụng của chúng tôi:
set search_path = anonym,"$user", public;
Cảnh báo :điều quan trọng là đường dẫn tìm kiếm được thiết lập chính xác trong định nghĩa nguồn dữ liệu trong ứng dụng. Người đọc được khuyến khích khám phá các cách nâng cao hơn để xử lý đường dẫn tìm kiếm, ví dụ:với việc sử dụng một hàm có thể xử lý logic động và phức tạp hơn. Ví dụ:bạn có thể chỉ định một nhóm người dùng nhập dữ liệu (hoặc vai trò) và cho phép họ tiếp tục sử dụng bảng public. person trong suốt khoảng thời gian ẩn danh (để họ tiếp tục xem dữ liệu bình thường), đồng thời xác định nhóm người dùng quản lý / báo cáo (hoặc vai trò) mà logic ẩn danh sẽ áp dụng.
Bây giờ, hãy truy vấn mối quan hệ cá nhân của chúng ta:
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=#
Bây giờ, giả sử rằng ông Singh rời công ty và thể hiện rõ ràng quyền được lãng quên của mình bằng một tuyên bố bằng văn bản. Ứng dụng thực hiện điều này bằng cách chèn id của anh ấy vào tập hợp id’s “bị lãng quên”:
testdb=# insert into anonym.person_anonym (id) VALUES(1);
INSERT 0 1
Bây giờ chúng ta hãy lặp lại truy vấn chính xác mà chúng ta đã chạy trước đây:
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=#
Chúng tôi có thể thấy rằng ứng dụng không thể truy cập thông tin chi tiết của Mr Singh.
Ẩn danh Toàn cầu Tạm thời
Ý tưởng Chính
- Người dùng đánh dấu thời điểm bắt đầu khoảng thời gian ẩn danh (một khoảng thời gian ngắn).
- Trong khoảng thời gian này, chỉ cho phép các lựa chọn đối với người có tên trong bảng.
- Tất cả quyền truy cập (lựa chọn) đều được ẩn danh cho tất cả các bản ghi trong bảng cá nhân, bất kể thiết lập ẩn danh trước đó.
- Người dùng đánh dấu phần cuối của khoảng thời gian ẩn danh.
Khối xây dựng
- Cam kết hai giai đoạn (còn gọi là Giao dịch chuẩn bị).
- Khóa bảng rõ ràng.
- Thiết lập ẩn danh mà chúng tôi đã thực hiện ở trên trong phần "Ẩn danh vĩnh viễn".
Triển khai
Một ứng dụng quản trị đặc biệt (ví dụ:được gọi là:markStartOfAnynimizationPeriod) thực hiện
testdb=# BEGIN ;
BEGIN
testdb=# LOCK public.person IN SHARE MODE ;
LOCK TABLE
testdb=# PREPARE TRANSACTION 'personlock';
PREPARE TRANSACTION
testdb=#
Những gì ở trên làm là có được một khóa trên bảng ở chế độ CHIA SẺ để chặn các BẢN NHẬP, CẬP NHẬT, XÓA. Ngoài ra, bằng cách bắt đầu giao dịch cam kết hai giai đoạn (giao dịch chuẩn bị của AKA, trong các ngữ cảnh khác được gọi là giao dịch phân tán hoặc giao dịch Kiến trúc eXtended XA), chúng tôi giải phóng giao dịch khỏi kết nối của phiên đánh dấu sự bắt đầu của giai đoạn ẩn danh, đồng thời cho phép các phiên tiếp theo khác nhận thức về sự tồn tại của nó. Giao dịch đã chuẩn bị là một giao dịch liên tục vẫn tồn tại sau khi ngắt kết nối / phiên đã bắt đầu (thông qua GIAO DỊCH CHUẨN BỊ). Lưu ý rằng câu lệnh "CHUẨN BỊ GIAO DỊCH" loại bỏ giao dịch khỏi phiên hiện tại. Giao dịch đã chuẩn bị có thể được thực hiện trong một phiên tiếp theo và có thể được hoàn trả hoặc được cam kết. Việc sử dụng loại giao dịch XA này cho phép hệ thống xử lý đáng tin cậy với nhiều nguồn dữ liệu XA khác nhau và thực hiện logic giao dịch trên các nguồn dữ liệu (có thể không đồng nhất) đó. Tuy nhiên, lý do chúng tôi sử dụng nó trong trường hợp cụ thể này:
- để cho phép phiên khách phát hành kết thúc phiên và ngắt kết nối / giải phóng kết nối của nó (để lại hoặc thậm chí tệ hơn là “duy trì” kết nối là một ý tưởng thực sự tồi, kết nối nên được giải phóng ngay khi nó hoạt động các truy vấn nó cần thực hiện)
- để tạo các phiên / kết nối tiếp theo có khả năng truy vấn sự tồn tại của giao dịch đã chuẩn bị này
- để làm cho phiên kết thúc có khả năng thực hiện giao dịch đã chuẩn bị này (bằng cách sử dụng tên của nó), do đó đánh dấu:
- phát hành khóa CHẾ ĐỘ CHIA SẺ
- kết thúc giai đoạn ẩn danh
Để xác minh rằng giao dịch còn tồn tại và được liên kết với khóa CHIA SẺ trên bảng cá nhân của chúng tôi, chúng tôi thực hiện:
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=#
Những gì truy vấn trên thực hiện là để đảm bảo rằng khóa cá nhân của giao dịch được chuẩn bị được đặt tên vẫn còn tồn tại và thực sự khóa liên quan trên người được giữ bởi giao dịch ảo này đang ở chế độ dự định:SHARE.
Vì vậy, bây giờ chúng ta có thể điều chỉnh chế độ xem:
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
Bây giờ với định nghĩa mới, nếu người dùng đã bắt đầu khóa người giao dịch chuẩn bị, thì lựa chọn sau sẽ trả về:
testdb=# select * from person;
id | givenname | midname | surname | address | email | role | rank
----+-----------+---------+---------+---------+-------+----------+-----------
1 | **** | **** | **** | **** | **** | Seafarer | Captain
2 | **** | **** | **** | **** | **** | IT | DBA
3 | **** | **** | **** | **** | **** | IT | Developer
(3 rows)
testdb=#
có nghĩa là ẩn danh vô điều kiện toàn cầu.
Bất kỳ ứng dụng nào cố gắng sử dụng dữ liệu từ người trong bàn sẽ nhận được "****" ẩn danh thay vì dữ liệu thực thực tế. Bây giờ, giả sử quản trị viên của ứng dụng này quyết định thời gian ẩn danh sắp kết thúc, vì vậy ứng dụng của anh ấy hiện gặp sự cố:
COMMIT PREPARED 'personlock';
Bây giờ, mọi lựa chọn tiếp theo sẽ trả về:
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=#
Cảnh báo! :Khóa ngăn không cho ghi đồng thời, nhưng không ngăn ghi cuối cùng khi khóa sẽ được giải phóng. Vì vậy, có một nguy cơ tiềm ẩn cho việc cập nhật ứng dụng, đọc '****' từ cơ sở dữ liệu, người dùng bất cẩn, nhấn cập nhật và sau đó sau một thời gian chờ đợi, khóa CHIA SẺ được phát hành và bản cập nhật thành công '*** * 'thay cho dữ liệu bình thường đúng. Tất nhiên người dùng có thể trợ giúp ở đây bằng cách không nhấn nút một cách mù quáng, nhưng một số biện pháp bảo vệ bổ sung có thể được thêm vào đây. Cập nhật ứng dụng có thể gây ra:
set lock_timeout TO 1;
khi bắt đầu cập nhật giao dịch. Bằng cách này, bất kỳ thời gian chờ / chặn nào lâu hơn 1ms sẽ tạo ra một ngoại lệ. Mà sẽ bảo vệ chống lại phần lớn các trường hợp. Một cách khác là ràng buộc kiểm tra trong bất kỳ trường nhạy cảm nào để kiểm tra giá trị ‘****’.
CẢNH BÁO! :điều bắt buộc là cuối cùng giao dịch đã chuẩn bị phải được hoàn thành. Hoặc bởi người dùng đã bắt đầu nó (hoặc một người dùng khác) hoặc thậm chí bởi một tập lệnh cron kiểm tra các giao dịch bị quên cứ sau 30 phút. Quên kết thúc giao dịch này sẽ gây ra kết quả thảm hại vì nó ngăn VACUUM chạy và tất nhiên khóa sẽ vẫn ở đó, ngăn không cho ghi vào cơ sở dữ liệu. Nếu bạn không đủ thoải mái với hệ thống của mình, nếu bạn không hiểu đầy đủ tất cả các khía cạnh và tất cả các tác dụng phụ của việc sử dụng giao dịch được chuẩn bị / phân phối có khóa, nếu bạn không có sự giám sát đầy đủ, đặc biệt là về MVCC số liệu, sau đó đơn giản là không tuân theo cách tiếp cận này. Trong trường hợp này, bạn có thể có tham số lưu giữ bảng đặc biệt cho mục đích quản trị, nơi bạn có thể sử dụng hai giá trị cột đặc biệt, một cho hoạt động bình thường và một cho ẩn danh được thực thi toàn cầu hoặc bạn có thể thử nghiệm với các khóa tư vấn chia sẻ cấp ứng dụng PostgreSQL:
- https://www.postgresql.org/docs/10/explicit-locking.html#ADVISORY-LOCKS
- https://www.postgresql.org/docs/10/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS