Tóm tắt: Tạo CA nội bộ dành riêng cho client, ký cert client có extendedKeyUsage=clientAuth. Trong nginx: ssl_client_certificate trỏ tới CA đó, ssl_verify_client on, chuyển danh tính client ($ssl_client_s_dn, $ssl_client_serial) xuống upstream. Test bằng curl --cert client.crt --key client.key. Khái niệm mTLS xem TLS và PKI; TLS phía server và reverse proxy xem Nginx reverse proxy + TLS.
Luồng hoạt động
sequenceDiagram participant C as Client (collector01) participant N as nginx participant U as Upstream API C->>N: ClientHello N->>C: Certificate (server) + CertificateRequest C->>N: Certificate (client.crt) + CertificateVerify N->>N: Verify với client-ca.crt, CRL, thời hạn alt hợp lệ N->>U: Request + X-Client-DN, X-Client-Serial U->>C: 200 else không có cert hoặc cert sai N->>C: 400 Bad Request end
Cấu hình
CA và cert client
Dùng CA riêng cho client, không dùng chung với CA ký cert server: nginx tin mọi cert do CA trong ssl_client_certificate ký.
# CA cho client (lab; production nên dùng Vault PKI hoặc step-ca)
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout client-ca.key -out client-ca.crt -days 1825 -subj "/CN=Example Client CA" \
-addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"
# Key + CSR trên máy client
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout client.key -out client.csr -subj "/CN=collector01/O=netops"
# CA ký, tự đặt extension (không chép từ CSR)
cat > client.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=clientAuth
subjectAltName=DNS:collector01.example.com
EOF
openssl x509 -req -in client.csr -CA client-ca.crt -CAkey client-ca.key -CAcreateserial \
-out client.crt -days 90 -extfile client.ext
openssl verify -CAfile client-ca.crt -purpose sslclient client.crt # client.crt: OKCác lệnh openssl khác (đọc cert, PKCS#12): OpenSSL cheatsheet.
nginx
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/tls/api.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/api.example.com/privkey.pem;
# mTLS
ssl_client_certificate /etc/nginx/tls/client-ca.crt; # CA tin cho cert client
ssl_verify_client on; # optional: cho phép không có cert
ssl_verify_depth 2;
# ssl_crl /etc/nginx/tls/client-ca.crl; # bật khi có quy trình thu hồi
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Serial $ssl_client_serial;
}
}Giá trị ssl_verify_client |
Hành vi |
|---|---|
on |
Bắt buộc cert hợp lệ, không có/không hợp lệ trả 400 |
optional |
Xin cert nhưng không bắt buộc; kiểm tra $ssl_client_verify trong config/app |
optional_no_ca |
Xin cert, không verify với CA (để app tự kiểm tra), ít dùng |
off |
Mặc định, không xin cert |
Chỉ bắt buộc mTLS cho một số path: đặt ssl_verify_client optional; ở server, rồi trong location:
location /admin/ {
if ($ssl_client_verify != SUCCESS) { return 403; }
proxy_pass http://127.0.0.1:8080;
}Phân quyền theo client cụ thể: map $ssl_client_s_dn $mtls_allowed { ... } hoặc để app đọc header X-Client-DN. Upstream phải chỉ nghe localhost/mạng nội bộ và ghi đè các header này, nếu không client có thể tự giả header.
sudo nginx -t && sudo systemctl reload nginxThu hồi cert client
# openssl ca cần file cấu hình tối thiểu: database (index.txt), crlnumber, default_md, default_crl_days
openssl ca -config ca.cnf -keyfile client-ca.key -cert client-ca.crt -revoke client.crt
openssl ca -config ca.cnf -keyfile client-ca.key -cert client-ca.crt -gencrl -out client-ca.crl
openssl verify -crl_check -CAfile client-ca.crt -CRLfile client-ca.crl client.crt # certificate revokedKhi bật ssl_crl, CRL hết hạn (nextUpdate qua) thì nginx từ chối mọi client: phải sinh lại CRL định kỳ rồi reload nginx. Cert client ngắn hạn (vài ngày tới 90 ngày) thường đơn giản hơn duy trì CRL.
Lệnh
# Có cert: 200
curl --cert client.crt --key client.key https://api.example.com/health
# Server dùng CA nội bộ
curl --cacert server-ca.crt --cert client.crt --key client.key https://api.example.com/health
# Không cert: 400 "No required SSL certificate was sent"
curl -sS -o /dev/null -w '%{http_code}\n' https://api.example.com/health
# Xem server có gửi CertificateRequest và danh sách CA chấp nhận
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | sed -n '/Acceptable client certificate CA names/,/Requested/p'
# Python requests
python3 -c 'import requests; print(requests.get("https://api.example.com/health", cert=("client.crt","client.key")).status_code)'Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
400 No required SSL certificate was sent |
Client không gửi cert (thiếu --cert, hoặc CA client không có trong danh sách server gửi) |
Kiểm tra lệnh client; xem "Acceptable client certificate CA names" |
400 The SSL certificate error |
Cert client không ký bởi ssl_client_certificate, hết hạn, thiếu intermediate, bị CRL |
error.log của nginx ghi lý do; openssl verify -CAfile client-ca.crt client.crt |
| Lỗi verify khi CA có intermediate | ssl_verify_depth quá nhỏ hoặc client không gửi intermediate |
Tăng depth; client gửi client.crt gộp intermediate |
unsupported certificate purpose |
Cert client thiếu clientAuth trong EKU |
Ký lại với extendedKeyUsage=clientAuth |
| Mọi client bị từ chối đột ngột | CRL hết hạn | Sinh lại CRL, tự động hoá bằng timer |
| Cert của CA server (Let's Encrypt) cũng vào được | ssl_client_certificate trỏ nhầm bundle CA public |
Chỉ để CA client nội bộ trong file này |
curl báo unable to set private key file |
Key không khớp cert hoặc sai định dạng | So hash public key (xem OpenSSL cheatsheet) |
Tham khảo
- ngx_http_ssl_module (
ssl_verify_client,ssl_client_certificate,ssl_crl, biến$ssl_client_*) man curl(--cert,--key,--cacert)- TLS troubleshooting