1. Triển khai Row Level Security (RLS) để cách ly dữ liệu
Chúng ta sẽ kích hoạt RLS trên bảng chứa dữ liệu nhạy cảm của tenant để đảm bảo mỗi người dùng chỉ nhìn thấy dữ liệu thuộc về tenant của họ, ngay cả khi họ có quyền SELECT trên bảng.
1.1. Chuẩn bị bảng và kích hoạt RLS
Tạo bảng mẫu nếu chưa có và kích hoạt tính năng RLS. Bước này bắt buộc trước khi tạo policy.
Mục đích: Kích hoạt cơ chế bảo vệ dữ liệu ở mức hàng (row-level).
Kết quả mong đợi: PostgreSQL trả về xác nhận bảng đã sẵn sàng cho RLS.
psql -U postgres -d tenant_db -c "
CREATE TABLE tenant_data (
id SERIAL PRIMARY KEY,
tenant_id UUID NOT NULL,
user_email VARCHAR(255),
sensitive_info TEXT
);
ALTER TABLE tenant_data ENABLE ROW LEVEL SECURITY;
"
Verify bằng lệnh:
psql -U postgres -d tenant_db -c "\d tenant_data"
Quan sát dòng Row Security: on trong output.
1.2. Tạo Policy cách ly theo Tenant
Định nghĩa chính sách (policy) để lọc dữ liệu dựa trên biến môi trường hoặc session variable của tenant_id.
Mục đích: Đảm bảo query SELECT, UPDATE, DELETE tự động lọc theo tenant_id hiện tại.
Kết quả mong đợi: Policy được tạo thành công, không hiển thị dữ liệu của tenant khác.
psql -U postgres -d tenant_db -c "
CREATE POLICY tenant_isolation_policy ON tenant_data
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
"
Verify bằng cách mô phỏng 2 tenant khác nhau:
psql -U postgres -d tenant_db -c "
INSERT INTO tenant_data (tenant_id, user_email) VALUES ('11111111-1111-1111-1111-111111111111', 'userA@tenant1.com');
INSERT INTO tenant_data (tenant_id, user_email) VALUES ('22222222-2222-2222-2222-222222222222', 'userB@tenant2.com');
"
Chạy query giả lập Tenant 1:
psql -U postgres -d tenant_db -c "
SET app.current_tenant_id = '11111111-1111-1111-1111-111111111111';
SELECT * FROM tenant_data;
"
Kết quả chỉ hiện 1 dòng của Tenant 1.
Chạy query giả lập Tenant 2:
psql -U postgres -d tenant_db -c "
SET app.current_tenant_id = '22222222-2222-2222-2222-222222222222';
SELECT * FROM tenant_data;
"
Kết quả chỉ hiện 1 dòng của Tenant 2.
2. Cấu hình Audit Log với pgaudit
Để theo dõi hành vi truy cập dữ liệu của từng tenant, chúng ta sẽ cài đặt và cấu hình extension pgaudit. Extension này ghi log chi tiết hơn default log của PostgreSQL.
2.1. Cài đặt và kích hoạt module pgaudit
Cài đặt gói pgaudit trên Ubuntu 24.04 và thêm module vào shared_preload_libraries.
Mục đích: Chuẩn bị môi trường để PostgreSQL có thể ghi log audit.
Kết quả mong đợi: Package được cài đặt, config file được cập nhật.
apt update && apt install -y postgresql-16-pgaudit
Sửa file cấu hình chính /etc/postgresql/16/main/postgresql.conf để thêm module:
cat >> /etc/postgresql/16/main/postgresql.conf
Khởi động lại PostgreSQL để áp dụng:
systemctl restart postgresql
2.2. Tạo Extension và cấu hình log theo Tenant
Kích hoạt extension trong database và cấu hình để ghi nhận log dựa trên tenant_id.
Mục đích: Cho phép PostgreSQL ghi log chi tiết vào file hoặc stdout.
Kết quả mong đợi: Extension được tạo, log audit xuất hiện khi thao tác.
psql -U postgres -d tenant_db -c "CREATE EXTENSION pgaudit;"
Cấu hình log để ghi nhận hoạt động của tenant cụ thể (thông qua session variable hoặc role):
psql -U postgres -d tenant_db -c "
SET pgaudit.log = 'ALL';
SET pgaudit.log_client_min_messages = 'notice';
"
Thực hiện một thao tác DML để test:
psql -U postgres -d tenant_db -c "SELECT * FROM tenant_data LIMIT 1;"
Verify log audit (vị trí log mặc định thường là /var/log/postgresql/postgresql-16-main.log):
tail -n 20 /var/log/postgresql/postgresql-16-main.log | grep pgaudit
Kết quả sẽ hiển thị dòng log có tiền tố LOG: pgaudit: kèm theo thông tin command và user.
3. Mã hóa dữ liệu (TDE) và SSL cho kết nối
Chúng ta sẽ kích hoạt SSL để mã hóa đường truyền (Encryption in Transit) và cấu hình TDE (Encryption at Rest) thông qua pgcrypto để mã hóa các cột nhạy cảm.
3.1. Cấu hình SSL cho kết nối Client
Tạo chứng chỉ tự ký (self-signed) và cấu hình PostgreSQL để yêu cầu SSL bắt buộc.
Mục đích: Ngăn chặn sniffing traffic giữa Client và Server.
Kết quả mong đợi: Kết nối không SSL bị từ chối.
Tạo thư mục chứng chỉ và key:
mkdir -p /etc/postgresql/16/main/ssl
cd /etc/postgresql/16/main/ssl
Tạo Self-signed Certificate (thay thế your_domain.com bằng IP hoặc domain thực tế của server):
openssl req -new -x509 -days 365 -nodes -text \
-keyout server.key -out server.crt \
-subj "/C=VN/ST=Ho Chi Minh City/L=District 1/O=TechCorp/OU=IT/CN=192.168.1.100"
Đặt quyền truy cập an toàn cho key (bắt buộc):
chmod 600 server.key
chown postgres:postgres server.key server.crt
Sửa file /etc/postgresql/16/main/postgresql.conf:
cat >> /etc/postgresql/16/main/postgresql.conf
Sửa file /etc/postgresql/16/main/pg_hba.conf để yêu cầu SSL cho tất cả kết nối:
sed -i 's/^host.*tenant_db.*$/# disabled non-ssl host/g' /etc/postgresql/16/main/pg_hba.conf
echo "hostssl all tenant_db 0.0.0.0/0 md5" >> /etc/postgresql/16/main/pg_hba.conf
Khởi động lại dịch vụ:
systemctl restart postgresql
Verify bằng cách kết nối qua SSL (tham số -c để kiểm tra):
psql "host=192.168.1.100 dbname=tenant_db sslmode=require user=postgres" -c "SHOW ssl;"
Kết quả phải trả về on. Nếu không dùng sslmode=require, kết nối sẽ bị từ chối.
3.2. Mã hóa dữ liệu tại chỗ (TDE) với pgcrypto
Sử dụng extension pgcrypto để mã hóa dữ liệu nhạy cảm (như sensitive_info) trước khi lưu vào database.
Mục đích: Bảo vệ dữ liệu ngay cả khi file data page bị đánh cắp.
Kết quả mong đợi: Dữ liệu trong database là dạng mã hóa, chỉ giải mã khi query đúng key.
psql -U postgres -d tenant_db -c "CREATE EXTENSION pgcrypto;"
Định nghĩa hàm Trigger để tự động mã hóa dữ liệu khi INSERT/UPDATE:
psql -U postgres -d tenant_db -c "
CREATE OR REPLACE FUNCTION encrypt_sensitive_data() RETURNS TRIGGER AS \$\$
DECLARE
encryption_key TEXT := 'my_secure_key_for_tenant_123'; -- Trong thực tế nên lấy từ secret manager
BEGIN
IF NEW.sensitive_info IS NOT NULL THEN
NEW.sensitive_info = pgp_sym_encrypt(NEW.sensitive_info, encryption_key);
END IF;
RETURN NEW;
END;
\$\$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER trigger_encrypt_data
BEFORE INSERT OR UPDATE ON tenant_data
FOR EACH ROW EXECUTE FUNCTION encrypt_sensitive_data();
"
Chèn dữ liệu thử nghiệm:
psql -U postgres -d tenant_db -c "
INSERT INTO tenant_data (tenant_id, user_email, sensitive_info)
VALUES ('11111111-1111-1111-1111-111111111111', 'test@test.com', 'Credit Card: 4111-1111-1111-1111');
"
Verify dữ liệu đã bị mã hóa:
psql -U postgres -d tenant_db -c "SELECT sensitive_info FROM tenant_data LIMIT 1;"
Kết quả hiển thị chuỗi ký tự ngẫu nhiên (PGP encrypted blob), không đọc được nội dung gốc.
Giải mã để xem lại (chỉ dùng cho admin hoặc logic backend):
psql -U postgres -d tenant_db -c "
SELECT pgp_sym_decrypt(sensitive_info, 'my_secure_key_for_tenant_123') as decrypted_info
FROM tenant_data LIMIT 1;
"
4. Chính sách mật khẩu mạnh và quản lý vòng đời
Cấu hình PostgreSQL để thực thi các chính sách mật khẩu mạnh (length, complexity) và tự động vô hiệu hóa tài khoản hết hạn.
4.1. Cài đặt module pg_password_policy
Ubuntu 24.04 có thể chưa có sẵn module này trong repo mặc định, chúng ta sẽ sử dụng extension pg_password_policy (cần build hoặc cài từ repo bên thứ 3) hoặc cấu hình thủ công qua pg_hba.conf và pg_ident.conf. Ở đây ta dùng phương pháp cấu hình trực tiếp trong postgresql.conf cho các tham số bảo mật cơ bản.
Mục đích: Ép buộc độ dài và độ phức tạp mật khẩu.
Kết quả mong đợi: Lệnh tạo user với mật khẩu yếu bị từ chối.
Cấu hình trong /etc/postgresql/16/main/postgresql.conf:
cat >> /etc/postgresql/16/main/postgresql.conf
Tạo script shell để kiểm tra độ phức tạp trước khi tạo user (giả lập policy):
cat > /usr/local/bin/check_password_strength.sh
Test script với mật khẩu yếu:
/usr/local/bin/check_password_strength.sh "weak123"
Kết quả: In ra lỗi và exit code 1.
Test script với mật khẩu mạnh:
/usr/local/bin/check_password_strength.sh "Str0ng!Pass#2024"
Kết quả: Không in gì, exit code 0.
4.2. Quản lý vòng đời tài khoản (Expiration)
Sử dụng tham số VALID UNTIL để đặt thời hạn sử dụng cho tenant user.
Mục đích: Tự động vô hiệu hóa tài khoản sau một khoảng thời gian nhất định.
Kết quả mong đợi: User không thể login sau ngày hết hạn.
psql -U postgres -d tenant_db -c "
CREATE ROLE tenant_admin_01 WITH LOGIN PASSWORD 'Str0ng!Pass#2024' VALID UNTIL '2024-12-31';
"
Verify thời hạn:
psql -U postgres -d tenant_db -c "\du tenant_admin_01"
Quan sát dòng valid until trong output.
Để gia hạn tài khoản:
psql -U postgres -d tenant_db -c "
ALTER ROLE tenant_admin_01 VALID UNTIL '2025-12-31';
"
5. Cô lập Traffic qua Network Security Group (Firewall)
Trên Ubuntu 24.04, chúng ta sử dụng UFW (Uncomplicated Firewall) để mô phỏng Security Group, chỉ cho phép traffic từ subnet của tenant hoặc application server đến cổng 5432.
5.1. Cấu hình UFW để cô lập PostgreSQL
Tắt tất cả traffic vào PostgreSQL, chỉ mở cho các IP cụ thể.
Mục đích: Ngăn chặn truy cập trái phép từ internet hoặc mạng nội bộ không được phép.
Kết quả mong đợi: Kết nối từ IP lạ bị từ chối (Connection timed out/refused).
Khởi động UFW và đặt policy mặc định:
ufw default deny incoming
ufw default allow outgoing
ufw enable
Cho phép SSH (để không bị khóa mình ra ngoài):
ufw allow 22/tcp
Giả sử Tenant 1 có subnet 10.0.1.0/24 và Tenant 2 có subnet 10.0.2.0/24. Mở cổng 5432 cho từng subnet:
ufw allow from 10.0.1.0/24 to any port 5432 proto tcp
ufw allow from 10.0.2.0/24 to any port 5432 proto tcp
Verify cấu hình:
ufw status verbose
Kết quả hiển thị danh sách các rule cho phép cổng 5432 từ các subnet đã định.
5.2. Test tính cô lập
Giả lập truy cập từ IP không nằm trong whitelist (ví dụ: 192.168.1.50).
Mục đích: Xác nhận firewall chặn kết nối không hợp lệ.
Kết quả mong đợi: Kết nối bị từ chối.
Thử kết nối từ IP lạ (trong thực tế sẽ fail, ở đây chỉ minh họa logic firewall):
telnet 192.168.1.100 5432
Nếu chạy trên cùng máy hoặc mạng không bị chặn bởi UFW, hãy dùng nmap từ máy khác để scan:
nmap -p 5432 192.168.1.100
Kết quả từ máy bên ngoài subnet whitelist sẽ hiện filtered hoặc closed.
Điều hướng series:
Mục lục: Series: Triển khai Database Multi-Tenant với PostgreSQL và Ubuntu 24.04
« Phần 6: Cấu hình Backup và Disaster Recovery cho môi trường Multi-Tenant
Phần 8: Giám sát, Phân tích hiệu năng và Troubleshooting »