Hardening MySQL và MariaDB cho Production: TLS, Auditing và Kiểm soát quyền truy cập

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

Vì sao cấu hình cài đặt mặc định của Database thất bại trong môi trường Production

Các bản cài đặt mới của MySQL hoặc MariaDB trên Ubuntu và Debian thường ưu tiên sự tiện lợi khi thiết lập hơn là tính bảo mật vững chắc. Ngay sau khi chạy apt install, máy chủ cơ sở dữ liệu thường lắng nghe trên các interface mở, truyền dữ liệu không mã hóa qua các mạng con VPC nội bộ và tắt query log để giảm thiểu tải I/O.

Những thiết lập mặc định thiên về tiện lợi này khiến cơ sở dữ liệu của bạn đối mặt với các rủi ro vận hành nghiêm trọng:

  • Đánh cắp lưu lượng truyền ở dạng Plaintext: Bất kỳ ai có quyền bắt gói tin (packet capture) trong mạng VPC, mạng overlay của Kubernetes (như Flannel hoặc Calico), hay subnet trên đám mây đều có thể xem trộm các truy vấn SQL không được mã hóa. Điều này làm lộ mật khẩu dạng plaintext, session token và thông tin nhận dạng cá nhân (PII) của khách hàng.
  • Tài khoản dịch vụ được cấp quá nhiều quyền: Khi một ứng dụng kết nối bằng ALL PRIVILEGES hoặc sử dụng host dạng wildcard ('app'@'%'), một lỗ hổng SQL injection trên một endpoint duy nhất có thể ngay lập tức dẫn đến việc bị dump toàn bộ cơ sở dữ liệu hoặc máy chủ từ xa bị xâm nhập.
  • Thiếu dấu vết kiểm toán (Audit Trail): Error log mặc định chỉ ghi lại sự cố sập tiến trình (daemon crash) và các sự kiện khởi động, nhưng lại bỏ qua các truy vấn. Khi ai đó vô tình xóa một bảng (drop table) vào lúc 2 giờ sáng hoặc dump 50.000 dòng dữ liệu người dùng, log tiêu chuẩn sẽ không thể chỉ ra ai đã làm điều đó, request xuất phát từ IP client nào hay truy vấn nào đã được thực thi.

Ba trụ cột trong việc Hardening cơ sở dữ liệu

Bảo mật các database instance không nhất thiết phải dùng đến những giải pháp enterprise đắt đỏ. Thay vào đó, hãy tập trung vào ba lớp nền tảng sau:

1. Transport Layer Security (TLS/SSL)

TLS mã hóa toàn bộ giao tiếp giữa các ứng dụng backend và database instance. Cơ chế này ngăn chặn việc nghe lén trên mạng đám mây riêng tư và xác thực máy chủ cơ sở dữ liệu bằng mật mã học trước khi trao đổi thông tin đăng nhập.

2. Nguyên tắc đặc quyền tối thiểu (Principle of Least Privilege – PoLP)

Chỉ cấp cho mỗi ứng dụng và kỹ sư chính xác những quyền hạn cần thiết cho vai trò của họ. Một dịch vụ báo cáo chỉ cần quyền SELECT trên một số bảng nhất định. Một backend API cần các quyền thao tác dữ liệu (DML – Data Manipulation Language) như SELECT, INSERT, UPDATE, DELETE, nhưng tuyệt đối không bao giờ được cấp quyền thay đổi cấu trúc bảng (DDL – DROP, ALTER) trong các hoạt động vận hành thường ngày.

3. Kiểm toán truy vấn liên tục (Continuous Query Auditing)

Audit logging ghi lại mọi lần đăng nhập, các thay đổi schema (DDL) và các truy vấn nhạy cảm vào một bản ghi bất biến. Dữ liệu này giúp tăng tốc độ phản ứng khi xảy ra sự cố và đáp ứng các tiêu chuẩn tuân thủ như SOC 2 Type II, ISO 27001 và HIPAA.

Hướng dẫn từng bước thực hiện Hardening

Bước 1: Cô lập mạng và phân quyền người dùng chi tiết

Bắt đầu bằng việc giới hạn các interface lắng nghe kết nối. Nếu ứng dụng và cơ sở dữ liệu dùng chung một máy ảo, hãy bind nghiêm ngặt vào localhost (127.0.0.1). Với các mô hình kiến trúc nhiều tầng (multi-tier), chỉ bind duy nhất vào địa chỉ IP VPC nội bộ của bạn (ví dụ: 10.0.1.25).

Cập nhật file cấu hình chính (/etc/mysql/mysql.conf.d/mysqld.cnf cho MySQL hoặc /etc/mysql/mariadb.conf.d/50-server.cnf cho MariaDB):

[mysqld]
# Chỉ bind vào interface mạng nội bộ riêng tư
bind-address = 10.0.1.25
port = 3306

# Ngăn chặn các lỗ hổng local file injection
local_infile = 0

# Vô hiệu hóa symbolic links để ngăn chặn kỹ thuật ghi đè bảng
skip_symbolic_links = 1

Tiếp theo, mở giao diện dòng lệnh quản trị cơ sở dữ liệu:

sudo mysql -u root -p

Tuyệt đối không sử dụng host dạng wildcard ('%') hoặc mật khẩu ngắn cho các tài khoản dịch vụ. Tôi thường sử dụng công cụ tạo mật khẩu trên trình duyệt tại toolcraft.app/vi/tools/security/password-generator để tạo các chuỗi 32 ký tự có độ phức tạp cao (high-entropy). Công cụ này tạo khóa hoàn toàn ở phía client mà không gửi bất kỳ dữ liệu nào qua mạng.

Chạy các câu lệnh sau để khởi tạo các tài khoản dịch vụ được cô lập, giới hạn theo IP và dọn dẹp các tài khoản mặc định:

-- Tạo user cho ứng dụng web gắn trực tiếp với IP máy chủ private của nó
CREATE USER 'shop_app'@'10.0.1.50' IDENTIFIED BY 'v8K9#mQ2$pL7!zR4*wT1@jN6^bX3(yU9';

-- Chỉ cấp quyền DML cần thiết trên các bảng production
GRANT SELECT, INSERT, UPDATE, DELETE ON shop_production.* TO 'shop_app'@'10.0.1.50';

-- Tạo user chỉ đọc (read-only) dành riêng cho các công cụ phân tích (Metabase, Looker)
CREATE USER 'bi_readonly'@'10.0.1.60' IDENTIFIED BY 'cN4$tP9*kR2!yW7#mQ5@jL8^vX1(bZ3)';
GRANT SELECT ON shop_production.* TO 'bi_readonly'@'10.0.1.60';

-- Xóa các tài khoản ẩn danh và database test mặc định
DROP USER IF EXISTS ''@'localhost', ''@'127.0.0.1', ''@'%';
DROP DATABASE IF EXISTS test;

-- Làm mới bảng phân quyền
FLUSH PRIVILEGES;

Bước 2: Bắt buộc mã hóa TLS 1.3

Chứng chỉ tự ký (self-signed) có thể dùng cho thử nghiệm ban đầu, nhưng môi trường production cần các chứng chỉ nội bộ chuyên dụng được ký bởi private PKI của bạn hoặc tạo thông qua OpenSSL.

Tạo một Certificate Authority (CA) riêng và cặp chứng chỉ máy chủ với khóa RSA 2048-bit:

sudo mkdir -p /etc/mysql/ssl
cd /etc/mysql/ssl

# 1. Tạo CA nội bộ
sudo openssl genrsa 2048 > ca-key.pem
sudo openssl req -new -x509 -nodes -days 1825 -key ca-key.pem -out ca.pem -subj "/CN=Internal-Database-CA"

# 2. Tạo server key và CSR
sudo openssl req -newkey rsa:2048 -days 1825 -nodes -keyout server-key.pem -out server-req.pem -subj "/CN=db-primary.internal"
sudo openssl rsa -in server-key.pem -out server-key.pem
sudo openssl x509 -req -in server-req.pem -days 1825 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem

# 3. Phân quyền và thiết lập quyền sở hữu file chặt chẽ
sudo chown -R mysql:mysql /etc/mysql/ssl
sudo chmod 600 /etc/mysql/ssl/*-key.pem
sudo chmod 644 /etc/mysql/ssl/*.pem

Cấu hình MySQL/MariaDB để load các chứng chỉ này và từ chối các cipher suite lỗi thời:

[mysqld]
ssl_ca = /etc/mysql/ssl/ca.pem
ssl_cert = /etc/mysql/ssl/server-cert.pem
ssl_key = /etc/mysql/ssl/server-key.pem

# Chỉ cho phép các phiên bản giao thức TLS hiện đại
tls_version = TLSv1.2,TLSv1.3

# Từ chối toàn bộ kết nối không mã hóa từ client trên toàn hệ thống
require_secure_transport = ON

Áp dụng cấu hình bằng cách khởi động lại dịch vụ database:

sudo systemctl restart mysql # Trên MariaDB: sudo systemctl restart mariadb

Thêm ràng buộc trên từng user để đảm bảo tài khoản ứng dụng chỉ có thể kết nối qua các kênh được mã hóa:

ALTER USER 'shop_app'@'10.0.1.50' REQUIRE SSL;
FLUSH PRIVILEGES;

Bước 3: Kích hoạt và cấu hình Audit Plugin

MariaDB Audit Plugin (server_audit) ghi lại các kết nối, ngắt kết nối và các truy vấn đã thực thi. Plugin này được tích hợp sẵn trong MariaDB và cũng có thể chạy trên các bản cài đặt MySQL tiêu chuẩn.

Thêm cấu hình audit vào file cấu hình server MySQL/MariaDB của bạn:

[mysqld]
# Tải thư viện audit plugin
plugin_load_add = server_audit=server_audit.so

# Kích hoạt ghi log
server_audit_logging = ON

# Ghi lại các lượt đăng nhập (CONNECT), truy vấn SQL (QUERY) và thay đổi DDL (TABLE)
server_audit_events = CONNECT,QUERY,TABLE

# Cấu hình file đích và xoay vòng log (tối đa 100MB mỗi file, lưu giữ 10 file xoay vòng)
server_audit_output_type = file
server_audit_file_path = /var/log/mysql/server_audit.log
server_audit_file_rotate_size = 104857600
server_audit_file_rotations = 10

# Loại trừ tài khoản bảo trì nội bộ để giảm nhiễu log
server_audit_excl_users = 'debian-sys-maint'

Khởi động lại dịch vụ hoặc nạp plugin trực tiếp trên server đang chạy mà không gây gián đoạn hoạt động (downtime):

INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
SET GLOBAL server_audit_file_path = '/var/log/mysql/server_audit.log';
SET GLOBAL server_audit_file_rotate_size = 104857600;
SET GLOBAL server_audit_file_rotations = 10;

Bước 4: Xác minh và kiểm thử phản hồi sự cố

Trước khi đưa vào vận hành thực tế trên production, hãy kiểm tra kỹ lưỡng từng ranh giới bảo mật.

1. Xác minh cơ chế bắt tay TLS:

SHOW STATUS LIKE 'Ssl_cipher';
-- Kết quả mong đợi: TLS_AES_256_GCM_SHA384 hoặc ECDHE-RSA-AES256-GCM-SHA384

2. Kiểm thử từ chối kết nối Plaintext:

# Thử kết nối không dùng SSL từ node ứng dụng
mysql -u shop_app -p -h 10.0.1.25 --ssl-mode=DISABLED
# Kết quả mong đợi: ERROR 3159 (HY000): Connections using insecure transport are prohibited while --require_secure_transport=ON.

3. Xác minh việc ghi Audit Log:

sudo tail -n 5 /var/log/mysql/server_audit.log

Bạn sẽ thấy các dòng dữ liệu định dạng CSV có cấu trúc ghi lại timestamp, hostname, username, IP nguồn, connection ID và nội dung truy vấn.

Checklist sẵn sàng cho môi trường Production

Hardening bảo mật là một quy trình vận hành liên tục. Hãy rà soát lại các chốt kiểm soát này trước khi trỏ ứng dụng thực tế vào cụm cơ sở dữ liệu của bạn:

  1. Ranh giới mạng: Cổng 3306 được bind vào IP private và được chặn khỏi internet công cộng bằng các quy tắc firewall (như ufw hoặc AWS Security Groups).
  2. Không dùng Wildcard: Bảng mysql.user không chứa bất kỳ tài khoản nào có định nghĩa host '%' hoặc để trống username.
  3. Bắt buộc mã hóa dữ liệu truyền tải: Thiết lập require_secure_transport = ON đang hoạt động, và các connection pool đã cấu hình sslmode=verify-full hoặc sslmode=verify-ca kèm chứng chỉ hợp lệ.
  4. Chuyển tiếp & Lưu trữ Log: Audit log nằm trên một volume ổ đĩa riêng biệt hoặc được đẩy về các hệ thống thu thập log tập trung (Vector, Filebeat, Grafana Loki) để tránh làm đầy dung lượng ổ cứng cục bộ.

Khi triển khai đầy đủ ba lớp bảo vệ này, cơ sở dữ liệu của bạn sẽ bảo vệ an toàn dữ liệu trên toàn mạng, ngăn chặn việc leo thang đặc quyền và lưu vết mọi truy vấn phục vụ việc tuân thủ kiểm toán.

Share: