Chiến lược đồng bộ Master Key trong cụm Raft
Trong môi trường phân tán, mỗi nút PostgreSQL cần truy cập vào cùng một Master Key để giải mã dữ liệu. Chúng ta không lưu trữ Master Key trực tiếp trên từng nút mà sử dụng cơ chế Vault hoặc một file chia sẻ qua mạng, nhưng để đảm bảo tính nhất quán và khả năng chịu lỗi, ta sẽ sử dụng một file khóa được đồng bộ qua Raft Log hoặc một hệ thống file hệ thống (Shared FS) được mount vào tất cả các nút.
Tại sao: Nếu mỗi nút có một Master Key riêng, dữ liệu được mã hóa trên Leader sẽ không thể giải mã được khi Follower trở thành Leader mới. Việc đồng bộ khóa đảm bảo tính liên tục của dịch vụ (High Availability).
Thực hiện tạo thư mục chứa khóa và file khóa mẫu trên Leader, sau đó cấu hình để nó được sao chép sang các Follower qua cơ chế đồng bộ file của Raft (hoặc qua shared storage).
mkdir -p /etc/postgres/keys
echo "AES256-CBC-MASTER-KEY-HASH-1234567890ABCDEF" > /etc/postgres/keys/master.key
chmod 600 /etc/postgres/keys/master.key
chown postgres:postgres /etc/postgres/keys/master.key
Kết quả mong đợi: File master.key được tạo với quyền truy cập chỉ dành cho chủ sở hữu (postgres), đảm bảo an toàn trước các truy cập trái phép từ hệ thống OS.
Cấu hình shared storage cho khóa
Để đảm bảo tất cả các nút trong cụm Raft đều có thể đọc cùng một file khóa mà không cần đồng bộ qua Log (tránh làm chậm WAL), ta sử dụng NFS hoặc GlusterFS mount vào cùng một đường dẫn trên tất cả các node.
Tại sao: Raft đồng bộ trạng thái dữ liệu (data), không đồng bộ file hệ thống bên ngoài. Việc mount cùng một volume chia sẻ giúp mọi nút truy cập Master Key giống nhau ngay lập tức khi khởi động.
nfs-server:/shared/keys /etc/postgres/keys nfs rw,soft,timeo=14 0 0
Thêm dòng trên vào file /etc/fstab trên tất cả các node (Leader và Follower) để mount tự động khi khởi động.
mount -a
ls -l /etc/postgres/keys
Kết quả mong đợi: Thư mục /etc/postgres/keys xuất hiện trên tất cả các node với cùng nội dung file master.key.
Xử lý khóa mã hóa khi Leader thay đổi
Khi Leader gặp sự cố và Raft bầu chọn một Follower mới làm Leader, nút mới này phải có khả năng giải mã ngay lập tức. Nếu Master Key được lưu trên shared storage (như phần trên), việc này xảy ra tự động. Tuy nhiên, nếu dùng cơ chế phân phối khóa qua Raft State Machine, ta cần đảm bảo khóa được lưu trong WAL trước khi commit transaction.
Tại sao: Trong quá trình failover, nếu nút mới không có khóa, dữ liệu sẽ bị khóa vĩnh viễn. Cần đảm bảo khóa luôn sẵn sàng trong bộ nhớ (RAM) hoặc được tải lại từ nơi lưu trữ an toàn ngay khi tiến trình PostgreSQL khởi động.
Cấu hình tham số khởi động để PostgreSQL tải khóa từ file đã mount vào bộ nhớ khi start up.
cat > /etc/postgresql/14/main/postgresql.conf
Tại sao: Tham số tde_master_key_file chỉ định vị trí cố định của khóa. Khi Follower trở thành Leader, nó sẽ đọc file này từ shared storage và đưa vào cache bộ nhớ, đảm bảo không có delay khi xử lý request đầu tiên.
Verify bằng cách restart PostgreSQL trên một Follower và kiểm tra log khởi động.
sudo systemctl restart postgresql@14-main
grep -i "master key" /var/log/postgresql/postgresql-14-main.log
Kết quả mong đợi: Log hiển thị dòng "Master key loaded successfully from /etc/postgres/keys/master.key" trên tất cả các node, xác nhận Follower mới có thể truy cập dữ liệu ngay lập tức.
Cấu hình Auto-Rotation cho Encryption Key
Việc xoay khóa (Key Rotation) là bắt buộc trong bảo mật, nhưng trong môi trường phân tán, thay đổi khóa có thể gây gián đoạn nếu không xử lý đúng cách. Chiến lược là tạo khóa mới, mã hóa lại dữ liệu cũ bằng khóa mới trong nền, và chỉ thay thế Master Key chính khi quá trình hoàn tất.
Tại sao: Không thể đơn giản là xóa khóa cũ và tạo khóa mới vì dữ liệu cũ vẫn được mã hóa bằng khóa cũ. Cần một cơ chế chuyển đổi (re-encryption) mượt mà.
Thực hiện thủ tục xoay khóa bằng cách tạo một Master Key mới và cập nhật metadata trong database để bắt đầu quá trình mã hóa lại dữ liệu mới.
cd /etc/postgres/keys
cp master.key master.key.old
openssl rand -base64 32 > master.key.new
chmod 600 master.key.new
chown postgres:postgres master.key.new
Tại sao: Chúng ta lưu lại khóa cũ (master.key.old) để giải mã dữ liệu hiện có, và tạo khóa mới (master.key.new) để mã hóa dữ liệu tương lai.
Thực thi lệnh trong PostgreSQL để kích hoạt quá trình xoay khóa. Lệnh này sẽ bắt đầu quá trình đọc dữ liệu cũ, giải mã bằng khóa cũ, và ghi lại bằng khóa mới vào WAL.
psql -U postgres -d your_database -c "SELECT pg_tde_rotate_key('/etc/postgres/keys/master.key.new', '/etc/postgres/keys/master.key.old');"
Kết quả mong đợi: Lệnh trả về trạng thái "Rotation started" và quá trình này chạy trong nền, không khóa transaction hay làm gián đoạn dịch vụ. WAL log sẽ bắt đầu ghi các bản ghi được mã hóa bằng khóa mới.
Hoàn tất và xóa khóa cũ
Khi quá trình xoay khóa hoàn tất (kiểm tra qua log hoặc function monitoring), ta cập nhật cấu hình để sử dụng khóa mới làm mặc định và xóa khóa cũ.
mv /etc/postgres/keys/master.key.new /etc/postgres/keys/master.key
rm /etc/postgres/keys/master.key.old
chown postgres:postgres /etc/postgres/keys/master.key
Verify bằng cách kiểm tra xem dữ liệu mới được ghi có được mã hóa bằng khóa mới không.
psql -U postgres -d your_database -c "INSERT INTO your_table (col1) VALUES ('new_data'); SELECT pg_tde_verify_key_hash();"
Kết quả mong đợi: Dữ liệu mới được ghi thành công và hàm verify trả về hash của khóa mới, xác nhận quá trình xoay khóa đã hoàn tất.
Đảm bảo tính toàn vẹn của WAL Log khi mã hóa
Khi sử dụng TDE, dữ liệu trong WAL log cũng phải được mã hóa để đảm bảo tính bảo mật khi dữ liệu ở trạng thái nghỉ (at rest). Tuy nhiên, việc này phải đảm bảo tính nhất quán: nếu một segment WAL bị hỏng hoặc mất, hệ thống không được cho phép khôi phục dữ liệu không hoàn chỉnh.
Tại sao: Nếu WAL bị mã hóa nhưng checksum không được tính toán đúng, quá trình recovery sau failover sẽ thất bại, dẫn đến mất dữ liệu. Cần đảm bảo cơ chế kiểm tra tính toàn vẹn (Integrity Check) hoạt động trên dữ liệu đã mã hóa.
Cấu hình PostgreSQL để bật tính năng checksum cho WAL và đảm bảo TDE extension áp dụng mã hóa cho WAL segment trước khi ghi disk.
cat >> /etc/postgresql/14/main/postgresql.conf
Tại sao: data_checksums = on tạo checksum cho từng page dữ liệu. Khi TDE mã hóa, checksum được tính trên dữ liệu đã mã hóa, giúp phát hiện lỗi bit hoặc tampering ngay cả khi dữ liệu đã được mã hóa.
Thực hiện restart PostgreSQL để áp dụng cấu hình mới. Lưu ý: Việc bật checksum trên cluster đang chạy có thể yêu cầu khởi tạo lại cluster, nhưng ở đây ta giả định đã cấu hình từ đầu hoặc đang trong giai đoạn bảo trì.
sudo systemctl restart postgresql@14-main
pg_controldata /var/lib/postgresql/14/main | grep "Data checksum"
Kết quả mong đợi: Dòng "Data checksum: on" xuất hiện trong đầu ra của pg_controldata, xác nhận checksum đã được bật.
Verify tính toàn vẹn của WAL khi có sự cố
Để kiểm tra tính toàn vẹn, ta có thể mô phỏng một sự cố nhỏ bằng cách ghi dữ liệu và sau đó kiểm tra xem WAL segment có được ghi đúng định dạng mã hóa và checksum không.
psql -U postgres -d your_database -c "CREATE TABLE test_integrity (id serial, data text); INSERT INTO test_integrity (data) VALUES ('sensitive_data'); CHECKPOINT;"
ls -l /var/lib/postgresql/14/main/pg_wal/
Tại sao: Lệnh CHECKPOINT buộc PostgreSQL ghi dữ liệu vào WAL và sau đó vào disk. Việc này tạo ra các WAL segment mới cần được mã hóa và checksum.
Kiểm tra log để đảm bảo không có lỗi checksum hoặc lỗi mã hóa trong quá trình ghi WAL.
grep -E "checksum|encryption error" /var/log/postgresql/postgresql-14-main.log
Kết quả mong đợi: Log không hiển thị bất kỳ lỗi nào liên quan đến checksum hoặc encryption. Nếu có lỗi, hệ thống sẽ từ chối ghi dữ liệu để bảo vệ tính toàn vẹn, và bạn sẽ thấy thông báo lỗi trong log.
Điều hướng series:
Mục lục: Series: Xây dựng Database phân tán an toàn với PostgreSQL, Raft và TDE
« Phần 4: Cấu hình TDE (Transparent Data Encryption) cho dữ liệu nghỉ
Phần 6: Bảo mật nâng cao: Quản lý truy cập và Audit trong hệ thống phân tán »