Hướng dẫn cấu hình Transparent Data Encryption (TDE) trong MySQL 8: Bảo mật dữ liệu nhạy cảm ở tầng lưu trữ (At-Rest)

MySQL tutorial - IT technology blog
MySQL tutorial - IT technology blog

Bối cảnh: Khi mã hóa database không còn là “nice-to-have”

Năm ngoái mình xử lý audit bảo mật cho một dự án SaaS lưu trữ thông tin hợp đồng và dữ liệu tài chính. Database production chạy MySQL 8.0 với khoảng 50GB data — không phải khổng lồ, nhưng đủ để gây rắc rối nếu lọt ra ngoài. Câu hỏi từ kiểm toán viên là: “Nếu ai đó lấy được file .ibd từ server, họ có đọc được data không?”

Câu trả lời mà mình không muốn nghe: Có.

InnoDB lưu dữ liệu trong các file tablespace (đuôi .ibd). Nếu không có encryption, bất kỳ ai có quyền đọc file hệ thống — kể cả nhân viên hosting, người tấn công chiếm được shell — đều có thể trích xuất data thô mà không cần MySQL password. Đây là lỗ hổng điển hình ở tầng at-rest (dữ liệu lúc lưu trữ), khác với at-transit (dữ liệu khi truyền qua mạng, đã có SSL/TLS lo).

Transparent Data Encryption (TDE) giải quyết đúng bài toán này. “Transparent” nghĩa là application không cần thay đổi một dòng code nào — MySQL tự mã hóa và giải mã hoàn toàn ở tầng storage engine.

TDE bảo vệ gì, không bảo vệ gì?

  • ✅ File tablespace (.ibd) bị copy ra ngoài
  • ✅ File redo log, undo log bị đọc trực tiếp từ disk
  • ✅ Physical backup (XtraBackup) nếu key không bị lấy theo
  • ❌ Query qua MySQL connection bình thường — data vẫn decrypt khi MySQL đọc lên RAM
  • ❌ Dump file từ mysqldump — đây là logical backup, data đã được giải mã trước khi export
  • ❌ Root user trên server có thể đọc memory process

Biết rõ giới hạn này trước để không bị ảo tưởng bảo mật toàn diện.

Cài đặt: Cấu hình Keyring Component

TDE trong MySQL 8 dựa vào keyring component để quản lý master encryption key. Từ MySQL 8.0.24+, các component thay thế hoàn toàn plugin cũ. Có mấy loại phổ biến:

  • component_keyring_file — Lưu key vào file local, đủ dùng cho hầu hết production
  • component_keyring_encrypted_file — File nhưng bản thân file key cũng được mã hóa
  • component_keyring_oci — Oracle Cloud Infrastructure Vault
  • component_keyring_aws — AWS KMS (yêu cầu MySQL Enterprise)

Mình sẽ hướng dẫn với component_keyring_file — phù hợp cho VPS hoặc bare-metal server.

Bước 1: Tạo file manifest cho MySQL server

# File này khai báo component nào MySQL sẽ load lúc khởi động
sudo nano /var/lib/mysql/mysqld.my

Nội dung file (JSON thuần):

{
    "components": "file://component_keyring_file"
}

Bước 2: Tạo thư mục lưu key và config component

# Quan trọng: đặt keyring NGOÀI datadir để tránh backup cùng data
sudo mkdir -p /etc/mysql/keyring
sudo chown mysql:mysql /etc/mysql/keyring
sudo chmod 750 /etc/mysql/keyring

# Tạo config cho keyring component
sudo nano /var/lib/mysql/component_keyring_file.cnf

Nội dung config:

{
    "path": "/etc/mysql/keyring/keyring_data",
    "read_only": false
}
# Phân quyền chặt cho các file config
sudo chown mysql:mysql /var/lib/mysql/mysqld.my
sudo chown mysql:mysql /var/lib/mysql/component_keyring_file.cnf
sudo chmod 600 /var/lib/mysql/mysqld.my
sudo chmod 600 /var/lib/mysql/component_keyring_file.cnf

Bước 3: Restart MySQL và xác nhận keyring đã load

sudo systemctl restart mysql

# Kiểm tra component status
mysql -u root -p -e "SELECT * FROM performance_schema.keyring_component_status;"

Nếu thấy Status: Activecomponent_keyring_file là MySQL đã sẵn sàng mã hóa.

Cấu hình chi tiết: Mã hóa Table, Tablespace và Log

Mã hóa từng table riêng lẻ

Đây là cách linh hoạt nhất — chỉ encrypt những table thực sự chứa data nhạy cảm, tránh overhead cho các table log hay cache.

-- Tạo table mới với encryption ngay từ đầu
CREATE TABLE users_sensitive (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    id_card VARCHAR(20),
    phone VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENCRYPTION='Y';

-- Bật encryption cho table đã có sẵn (online operation, không lock lâu)
ALTER TABLE payment_info ENCRYPTION='Y';

-- Tắt encryption nếu cần (ví dụ migrate sang system khác)
ALTER TABLE payment_info ENCRYPTION='N';

Bật encryption mặc định toàn server

Thêm vào /etc/mysql/mysql.conf.d/mysqld.cnf để mọi table mới tạo đều tự động được encrypt:

[mysqld]
# Tất cả InnoDB table tạo mới sẽ tự động encrypt
default_table_encryption = ON

# Encrypt binary log — quan trọng nếu dùng replication
binlog_encryption = ON

# Encrypt undo tablespace (lưu transaction rollback data)
innodb_undo_log_encrypt = ON

# Encrypt redo log (lưu write-ahead log của InnoDB)
innodb_redo_log_encrypt = ON
sudo systemctl restart mysql

Với default_table_encryption = ON, table cũ vẫn giữ nguyên trạng thái — cần ALTER thủ công. Mình thường viết script để migrate batch từng table một, tránh tạo pressure disk I/O đột biến.

Rotate Master Key định kỳ

Best practice bảo mật là rotate encryption key định kỳ. MySQL TDE dùng mô hình 2 lớp key:

  • Master Key — do keyring quản lý, dùng để wrap tablespace key
  • Tablespace Encryption Key (TEK) — mỗi tablespace có 1 TEK, lưu encrypted trong header file .ibd

Khi rotate master key, MySQL tạo master key mới và re-encrypt tất cả TEK. Data pages không bị re-encrypt — nên thao tác rất nhanh dù database lớn:

-- Rotate master key (online, không cần downtime)
ALTER INSTANCE ROTATE INNODB MASTER KEY;

Mình thêm lệnh này vào crontab chạy hàng tháng:

# Thêm vào /etc/cron.d/mysql-key-rotation
0 3 1 * * root mysql -u root -p'YOUR_PASSWORD' -e "ALTER INSTANCE ROTATE INNODB MASTER KEY;" >> /var/log/mysql-key-rotation.log 2>&1

Kiểm tra & Monitoring

Xác nhận table đã được encrypt

-- Xem tất cả tablespace đang encrypt
SELECT 
    SPACE,
    NAME,
    ENCRYPTION
FROM information_schema.INNODB_TABLESPACES
WHERE ENCRYPTION = 'Y'
ORDER BY NAME;

-- Kiểm tra table cụ thể trong một database
SELECT 
    TABLE_SCHEMA,
    TABLE_NAME,
    CREATE_OPTIONS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database'
    AND CREATE_OPTIONS LIKE '%ENCRYPTION%';

Verify bằng cách đọc file .ibd trực tiếp

Test thực tế nhất — thử dùng strings để đọc nội dung file tablespace:

# Tìm đường dẫn file .ibd
sudo find /var/lib/mysql -name "users_sensitive.ibd"

# Đọc file đã encrypt — chỉ thấy binary garbage
sudo strings /var/lib/mysql/your_db/users_sensitive.ibd | head -20

# So sánh với table KHÔNG encrypt
# (thay your_db và table_name phù hợp)
sudo strings /var/lib/mysql/your_db/non_encrypted_table.ibd | grep -i "@gmail"

Đây là bài demo mình hay dùng với khách hàng: nhìn thấy file .ibd chỉ toàn binary random thay vì plaintext email address — thuyết phục hơn bất kỳ document nào.

Monitor performance impact

TDE có overhead do AES encryption/decryption, nhưng CPU hiện đại đều có AES-NI hardware instruction, nên impact thực tế rất nhỏ:

# Kiểm tra CPU có hỗ trợ AES-NI không
grep -m 1 aes /proc/cpuinfo
# Output mong đợi: flags: ... aes ... (có chữ 'aes' trong flags)
-- Theo dõi I/O performance sau khi bật TDE
SELECT 
    EVENT_NAME,
    COUNT_READ,
    ROUND(SUM_TIMER_READ / 1000000000, 2) AS read_time_ms,
    COUNT_WRITE,
    ROUND(SUM_TIMER_WRITE / 1000000000, 2) AS write_time_ms
FROM performance_schema.file_summary_by_event_name
WHERE EVENT_NAME LIKE '%innodb%datafile%'
ORDER BY SUM_TIMER_READ DESC
LIMIT 10;

Với database 50GB của mình, sau khi bật TDE đầy đủ (bao gồm redo log và undo log), query time tăng khoảng 2–3% — nằm trong noise threshold và hoàn toàn chấp nhận được để đổi lấy compliance bảo mật.

Backup keyring file — bước hay bị bỏ qua nhất

Điểm quan trọng nhất mà nhiều người bỏ sót: keyring file phải backup riêng biệt, KHÔNG cùng data files. Mất keyring file đồng nghĩa với mất toàn bộ data đã encrypt — dù có đủ backup .ibd cũng không recover được.

# Backup keyring sau mỗi lần rotate key
sudo cp /etc/mysql/keyring/keyring_data \
    /backup/keyring/keyring_data_$(date +%Y%m%d_%H%M%S)
sudo chmod 600 /backup/keyring/keyring_data_$(date +%Y%m%d_%H%M%S)

# Liệt kê các bản backup
sudo ls -la /backup/keyring/

Tốt nhất là lưu keyring backup ở một server hoặc storage hoàn toàn khác với database server. Nguyên tắc “separation of duties”: key và data nên ở hai chỗ khác nhau — đây cũng là yêu cầu của hầu hết tiêu chuẩn bảo mật như PCI-DSS hay ISO 27001.

Share: