Bảo mật PostgreSQL Server trên Linux: Thực chiến với pg_hba.conf, pgcrypto và pgaudit

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Bối cảnh: Sai lầm phổ biến khi triển khai PostgreSQL

Nhiều bạn khi setup PostgreSQL trên Ubuntu thường giữ nguyên cấu hình mặc định. Điển hình là mở listen_addresses = '*', dùng mật khẩu dễ đoán hoặc lưu số CCCD, mã thẻ ngân hàng dưới dạng văn bản thô (plaintext).

Hậu quả rất khó lường. Nếu server có IP public hoặc nằm chung dải mạng nội bộ, chỉ một lỗi SQL Injection nhỏ cũng đủ khiến toàn bộ dữ liệu bị lộ sạch.

Trong các dự án thực tế, mình luôn dựng hàng rào bảo vệ theo 3 lớp:

  • Tầng mạng & xác thực: Dùng pg_hba.conf để giới hạn chính xác IP truy cập, đồng thời bắt buộc mã hóa mật khẩu bằng chuẩn scram-sha-256.
  • Tầng dữ liệu lưu trữ (Data at Rest): Mã hóa từng cột nhạy cảm bằng extension pgcrypto. Dù hacker có lấy được file backup .sql dung lượng hàng chục GB thì dữ liệu bên trong vẫn là chuỗi vô nghĩa.
  • Tầng giám sát & truy vết (Audit): Cài đặt extension pgaudit để ghi log mọi truy vấn can thiệp schema và dữ liệu.

Cài đặt các gói và extension cần thiết

Mình thực hiện bài lab này trên Ubuntu 22.04 với PostgreSQL 16. Quy trình trên bản 14 hoặc 15 cũng tương tự.

# Cập nhật repo và cài đặt PostgreSQL 16 cùng extension
sudo apt update
sudo apt install -y postgresql-16 postgresql-contrib postgresql-16-pgaudit

# Khởi động và kích hoạt service chạy cùng hệ thống
sudo systemctl enable postgresql
sudo systemctl start postgresql

Gói postgresql-contrib chứa sẵn pgcrypto, còn postgresql-16-pgaudit là công cụ kiểm toán chính thức do cộng đồng PostgreSQL phát triển.

Đừng bao giờ dùng những mật khẩu như postgres123 hay admin@2026. Hãy tạo một chuỗi ngẫu nhiên dài từ 24–32 ký tự bao gồm chữ hoa, chữ thường, số và ký tự đặc biệt.

Để tạo nhanh, mình hay dùng Password Generator trên ToolCraft. Công cụ này xử lý hoàn toàn trên trình duyệt (client-side), không truyền dữ liệu về server nên rất an toàn.

# Đổi mật khẩu cho user postgres bằng chuỗi mạnh vừa tạo
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'k8#M9$xP2@vL7!qZ4*wR1&yN6';"

Triển khai 3 lớp phòng thủ cho database

1. Siết chặt truy cập mạng qua postgresql.conf và pg_hba.conf

PostgreSQL quản lý cấu hình kết nối qua hai file chính tại /etc/postgresql/16/main/.

Đầu tiên, bạn mở file postgresql.conf:

sudo nano /etc/postgresql/16/main/postgresql.conf

Thay vì mở *, bạn chỉ định rõ IP private của database server (ví dụ 10.0.1.5) và chuyển thuật toán băm mật khẩu sang SCRAM-SHA-256:

# Chỉ lắng nghe kết nối nội bộ từ localhost và IP private
listen_addresses = 'localhost, 10.0.1.5'

# Bật chuẩn băm mật khẩu an toàn nhất hiện nay của Postgres
password_encryption = scram-sha-256

Kế tiếp, thiết lập danh sách trắng (whitelist) IP trong pg_hba.conf:

sudo nano /etc/postgresql/16/main/pg_hba.conf

Mỗi dòng tuân theo cú pháp: TYPE DATABASE USER ADDRESS METHOD. Hãy xóa các dòng dùng phương thức trust hoặc md5, sau đó cấu hình theo mẫu an toàn bên dưới:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
# Local socket cho user hệ thống
local   all             postgres                                peer
local   all             all                                     scram-sha-256

# Chỉ cho phép cụm Backend Server (dải 10.0.2.0/24) truy cập app_production
host    app_production  app_user        10.0.2.0/24             scram-sha-256

# Chặn toàn bộ IP còn lại
host    all             all             all                     reject

Khi cần chia nhỏ subnet cho cụm microservices hoặc server backend, bạn có thể tra cứu nhanh qua Subnet Calculator của ToolCraft để lấy đúng dải CIDR và subnet mask.

2. Mã hóa dữ liệu nhạy cảm theo từng cột với pgcrypto

Nếu server bị chiếm quyền root hoặc lộ file backup SQL, mã hóa ổ đĩa (LUKS) sẽ không còn tác dụng. Lúc này, mã hóa cấp độ ứng dụng/cột với pgcrypto là giải pháp cứu cánh.

Vào database của ứng dụng để bật extension:

\c app_production
CREATE EXTENSION IF NOT EXISTS pgcrypto;

Tạo bảng lưu hồ sơ người dùng với cột số căn cước (ID card) ở định dạng nhị phân BYTEA:

CREATE TABLE customer_profiles (
    id SERIAL PRIMARY KEY,
    full_name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    encrypted_id_card BYTEA NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Chèn dữ liệu có mã hóa đối xứng AES-128 bằng pgp_sym_encrypt
INSERT INTO customer_profiles (full_name, email, encrypted_id_card)
VALUES (
    'Nguyen Van A',
    '[email protected]',
    pgp_sym_encrypt('079199001234', 'SecretKey_2026_Prod_DbApp')
);

Truy vấn giải mã dữ liệu:

SELECT 
    id,
    full_name,
    email,
    pgp_sym_decrypt(encrypted_id_card, 'SecretKey_2026_Prod_DbApp') AS id_card_decrypted
FROM customer_profiles
WHERE email = '[email protected]';

Khóa bí mật nên được lưu ở biến môi trường của ứng dụng backend (như Vault hoặc AWS Secrets Manager). Tuyệt đối không hardcode khóa trong file SQL.

3. Giám sát và ghi log chi tiết bằng pgaudit

Extension pgaudit hỗ trợ ghi lại chi tiết câu lệnh, user thực thi và bảng bị tác động. Đây là yêu cầu bắt buộc nếu bạn cần chuẩn hóa theo chứng chỉ SOC2 hoặc PCI-DSS.

Thêm cấu hình nạp thư viện vào cuối file /etc/postgresql/16/main/postgresql.conf:

# Nạp pgaudit khi start PostgreSQL
shared_preload_libraries = 'pgaudit'

# Ghi log DDL, ROLE và thao tác ghi dữ liệu (INSERT/UPDATE/DELETE)
pgaudit.log = 'ddl, role, write'
pgaudit.log_catalog = off
pgaudit.log_level = 'log'
pgaudit.log_parameter = on

Khởi động lại service để nạp module vào RAM:

sudo systemctl restart postgresql

Kích hoạt extension trên database cần giám sát:

\c app_production
CREATE EXTENSION IF NOT EXISTS pgaudit;

Kiểm thử thực tế và lưu ý vận hành

1. Thử nghiệm kết nối từ IP trái phép

Từ một máy chủ ngoài dải whitelist (ví dụ 192.168.1.50), thử gõ lệnh kết nối:

psql -h 10.0.1.5 -U app_user -d app_production

Server sẽ ngắt kết nối ngay lập tức:

psql: error: connection to server at "10.0.1.5", port 5432 failed: FATAL: no pg_hba.conf entry for host "192.168.1.50", user "app_user", database "app_production", no encryption

2. Kiểm tra log kiểm toán

Mở một cửa sổ terminal khác để theo dõi log real-time:

sudo tail -f /var/log/postgresql/postgresql-16-main.log

Thực hiện một truy vấn tạo và xóa bảng:

CREATE TABLE test_audit (id INT);
DROP TABLE test_audit;

File log sẽ ghi lại cấu trúc rõ ràng:

2026-10-06 10:45:12.312 UTC [12450] app_user@app_production LOG:  AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.test_audit,CREATE TABLE test_audit (id INT);,<not logged>
2026-10-06 10:45:18.891 UTC [12450] app_user@app_production LOG:  AUDIT: SESSION,2,1,DDL,DROP TABLE,TABLE,public.test_audit,DROP TABLE test_audit;,<not logged>

Lưu ý về dung lượng đĩa: Với hệ thống có khoảng 1.000 transaction/giây, file log của pgaudit có thể tăng 2–5 GB mỗi ngày. Bạn hãy cấu hình thêm logrotate kèm chính sách nén gzip để tránh tình trạng đầy ổ cứng (disk full).

Share: