Xử lý lỗi không khớp tên miền (Hostname Mismatches)Gần đây tôi đã gặp một trở ngại khi đang cấu hình môi trường Docker-compose tại địa phương cho một dự án microservices. Một dịch vụ nội bộ đã cố gắng lấy dữ liệu từ một dịch vụ khác qua HTTPS, và Node.js đã ngay lập tức ngắt kết nối với lỗi sau:
Error: Hostname/IP does not match certificate's altnames: Host: api.example.com is not in the cert's altnames: DNS:example.com
Node.js kích hoạt lỗi ERR_TLS_CERT_ALTNAME_INVALID để bảo vệ bạn. Hãy coi nó như một quá trình bắt tay bảo mật. Máy khách (client) xác minh xem máy chủ (server) mà bạn đang kết nối có thực sự được phép sử dụng hostname cụ thể đó hay không. Nếu bạn yêu cầu api.example.com nhưng chứng chỉ chỉ bao gồm example.com, Node.js sẽ hủy kết nối để chặn các cuộc tấn công xen giữa (man-in-the-middle attacks) tiềm ẩn.
Tại sao các môi trường hiện đại lại từ chối chứng chỉ của bạnTrước đây, các trình duyệt và môi trường thực thi dựa vào trường Common Name (CN) để xác minh danh tính. Nhưng điều đó không còn đúng nữa. Các hệ thống hiện đại như Node.js và các trình duyệt dựa trên Chromium hiện yêu cầu tiện ích mở rộng Subject Alternative Name (SAN). Nếu hostname mục tiêu của bạn không được liệt kê rõ ràng trong mảng SAN đó, kết nối sẽ thất bại—ngay cả khi CN khớp hoàn toàn.
Bạn có thể sẽ gặp phải điều này trong ba tình huống phổ biến:
- Phát triển cục bộ: Sử dụng chứng chỉ tự ký cho
localhosthoặc các tên miền.localtùy chỉnh.- Sự phát sinh subdomain: Thêmapi.hoặcstaging.vào hạ tầng của bạn mà không cấp lại chứng chỉ.- Truy cập trực tiếp qua IP: Kết nối qua192.168.1.50khi chứng chỉ chỉ xác thực các tên miền DNS.## Cách khắc phục "Tạm thời" mà bạn nên tránhStack Overflow đầy rẫy những lời khuyên gợi ý bạn chỉ cần bật một công tắc toàn cục để bỏ qua bảo mật. Bạn có thể thấy đoạn mã này được đề xuất:
// TUYỆT ĐỐI KHÔNG sử dụng cái này trong môi trường production
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
Hoặc đoạn này trong logic yêu cầu của bạn:
const agent = new https.Agent({
rejectUnauthorized: false
});
Đừng làm vậy. Điều này thực chất sẽ loại bỏ chữ "S" khỏi HTTPS bằng cách vô hiệu hóa tất cả các bước xác thực chứng chỉ. Nó khiến lưu lượng truy cập của bạn hoàn toàn bị hở cho các hành vi đánh chặn. Chỉ sử dụng nó để kiểm tra chẩn đoán trong 10 giây nếu bạn bắt buộc phải làm vậy, nhưng giải pháp thực sự nằm ở chính bản thân chứng chỉ.
Bước 1: Tạo chứng chỉ với SANKhi sử dụng OpenSSL để phát triển, bạn phải xác định rõ tiện ích mở rộng SAN. Tôi thích sử dụng một tệp cấu hình để giữ cho quy trình có thể lặp lại và ít bị lỗi đánh máy hơn.
1. Tạo tệp cấu hình (server.conf)Tạo một tệp có tên server.conf. Thay thế api.example.com bằng tên dịch vụ cục bộ của bạn. Việc bao gồm 127.0.0.1 và localhost là một thói quen tốt cho các môi trường phát triển.
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = api.example.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = api.example.com
DNS.2 = localhost
IP.1 = 127.0.0.1
2. Tạo khóa và chứng chỉChạy lệnh này để tạo khóa RSA 2048-bit và chứng chỉ có thời hạn 365 ngày bằng cấu hình của bạn:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout server.key \
-out server.crt \
-config server.conf \
-extensions v3_req
Lệnh này tạo ra tệp server.crt. Không giống như chứng chỉ tự ký tiêu chuẩn, chứng chỉ này chứa siêu dữ liệu mà Node.js cần để xác minh danh tính của api.example.com.
Bước 2: Xác thực tùy chỉnh cho các trường hợp đặc biệtĐôi khi bạn bị kẹt với kết nối dựa trên IP trong một môi trường cũ (legacy), nhưng chứng chỉ lại bị khóa theo tên miền. Nếu bạn không thể cấp lại chứng chỉ, bạn có thể sử dụng checkServerIdentity để giải quyết vấn đề theo cách thủ công mà không phá hỏng bảo mật của mình.
Hàm này cho phép bạn xác định chính xác cách Node.js nên xác thực danh tính của máy chủ:
const https = require('https');
const fs = require('fs');
const tls = require('tls');
const options = {
hostname: '10.0.0.5',
port: 443,
ca: fs.readFileSync('server.crt'),
checkServerIdentity: (host, cert) => {
// Nếu chúng ta đang truy cập IP nội bộ, hãy cho phép nó một cách thủ công cho chứng chỉ này
if (host === '10.0.0.5') {
return undefined; // Xác thực thành công
}
// Nếu không, hãy sử dụng logic tiêu chuẩn của Node.js
return tls.checkServerIdentity(host, cert);
}
};
Việc trả về undefined sẽ báo cho Node.js rằng danh tính đã được xác minh. Nếu danh tính không chính xác, hàm nên trả về một đối tượng Error để chấm dứt kết nối.
Xác minh: Đừng đoán, hãy kiểm traTrước khi khởi động lại ứng dụng Node.js của bạn, hãy kiểm tra dữ liệu thô của chứng chỉ. Việc này chỉ mất năm giây và giúp tránh hàng giờ gỡ lỗi.
openssl x509 -in server.crt -text -noout
Cuộn xuống phần X509v3 Subject Alternative Name. Bạn sẽ thấy một danh sách tương tự như sau:
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:api.example.com, DNS:localhost, IP Address:127.0.0.1
Nếu mục DNS: hoặc IP Address: cụ thể đó bị thiếu, Node.js sẽ tiếp tục từ chối kết nối với lỗi ERR_TLS_CERT_ALTNAME_INVALID.

