Pixiv @chokei
1521 字
8 min
mTSL使用基本教程
mTLS
什么是双向TLS
基本概念
双向TLS(Mutual TLS,简称mTLS)是一种双向身份验证机制:
- 单向TLS:仅服务器向客户端证明身份
- 双向TLS:服务器和客户端相互验证身份
工作原理
客户端 <---> 服务器 ↓ ↓客户端证书 服务器证书 ↓ ↓ CA验证 CA验证证书准备
创建CA
# 创建工作目录mkdir -p ~/mtls/{ca,server,client}cd ~/mtls
# 生成CA私钥openssl genrsa -out ca/ca.key 4096
# 生成CA证书openssl req -new -x509 -days 3650 -key ca/ca.key -out ca/ca.crt \ -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=IT/CN=MyCA"
# 查看CA证书信息openssl x509 -in ca/ca.crt -text -noout生成服务器证书
# 生成服务器私钥openssl genrsa -out server/server.key 2048
# 创建证书签名请求(CSR)openssl req -new -key server/server.key -out server/server.csr \ -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=IT/CN=example.com"
# 创建扩展配置文件cat > server/server_ext.cnf <<EOFauthorityKeyIdentifier=keyid,issuerbasicConstraints=CA:FALSEkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEnciphermentsubjectAltName = @alt_names
[alt_names]DNS.1 = example.comDNS.2 = *.example.comIP.1 = 192.168.1.100EOF
# 使用CA签发服务器证书openssl x509 -req -days 365 \ -in server/server.csr \ -CA ca/ca.crt \ -CAkey ca/ca.key \ -CAcreateserial \ -out server/server.crt \ -extfile server/server_ext.cnf生成客户端证书
# 生成客户端私钥openssl genrsa -out client/client.key 2048
# 创建客户端CSRopenssl req -new -key client/client.key -out client/client.csr \ -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=IT/CN=client1"
# 签发客户端证书openssl x509 -req -days 365 \ -in client/client.csr \ -CA ca/ca.crt \ -CAkey ca/ca.key \ -CAcreateserial \ -out client/client.crt
# 生成PKCS12格式openssl pkcs12 -export \ -out client/client.p12 \ -inkey client/client.key \ -in client/client.crt \ -certfile ca/ca.crt \ -passout pass:123456服务器配置
Nginx配置
server { listen 443 ssl; server_name example.com;
# SSL证书配置 ssl_certificate /path/to/server/server.crt; ssl_certificate_key /path/to/server/server.key;
# 客户端证书验证 ssl_client_certificate /path/to/ca/ca.crt; ssl_verify_client on; ssl_verify_depth 2;
# SSL协议和加密套件 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
# Session配置 ssl_session_timeout 10m; ssl_session_cache shared:SSL:10m;
location / { # 传递客户端证书信息到后端 proxy_set_header X-SSL-Client-Cert $ssl_client_cert; proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn; proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_pass http://backend; }
# 可选:根据客户端证书CN进行访问控制 location /admin { if ($ssl_client_s_dn !~ "CN=admin") { return 403; } proxy_pass http://backend; }}Apache配置
<VirtualHost *:443> ServerName example.com DocumentRoot /var/www/html
# 启用SSL SSLEngine on SSLCertificateFile /path/to/server/server.crt SSLCertificateKeyFile /path/to/server/server.key
# 客户端证书验证 SSLCACertificateFile /path/to/ca/ca.crt SSLVerifyClient require SSLVerifyDepth 2
# SSL协议配置 SSLProtocol all -SSLv2 -SSLv3 SSLCipherSuite HIGH:!aNULL:!MD5
<Directory /var/www/html> # 基于客户端证书的访问控制 SSLRequire %{SSL_CLIENT_S_DN_CN} eq "client1" \ or %{SSL_CLIENT_S_DN_CN} eq "client2" </Directory></VirtualHost>Node.js配置
const https = require('https');const fs = require('fs');const express = require('express');
const app = express();
// 配置选项const options = { key: fs.readFileSync('server/server.key'), cert: fs.readFileSync('server/server.crt'), ca: fs.readFileSync('ca/ca.crt'), requestCert: true, rejectUnauthorized: true};
// 中间件:验证客户端证书app.use((req, res, next) => { const cert = req.socket.getPeerCertificate();
if (req.client.authorized) { console.log(`Client CN: ${cert.subject.CN}`); next(); } else { res.status(401).send('Client certificate required'); }});
app.get('/', (req, res) => { res.send('mTLS connection successful!');});
https.createServer(options, app).listen(443, () => { console.log('Server running on https://localhost:443');});Cloudflare
Cloudflare mTLS设置
登录Cloudflare Dashboard
- 访问 Cloudflare Dashboard
- 选择你的域名
配置SSL/TLS
SSL/TLS → Overview: - 加密模式: Full (strict)
SSL/TLS → Client Certificates: 1. 点击 "Create Certificate" 2. 生成或上传客户端CA证书 3. 配置证书参数: - Certificate name: "My mTLS CA" - Certificate: [粘贴ca.crt内容] - Private key: [保持为空,只需要公钥]创建mTLS规则
SSL/TLS → Client Certificates → Create mTLS Rule: 名称: "Require Client Cert"
如果传入请求匹配: - 主机名: example.com - URI路径: /api/*
则: - 需要客户端证书: 开启 - CA证书: "My mTLS CA"使用Cloudflare API配置
# 设置环境变量export CF_EMAIL="your-email@example.com"export CF_API_KEY="your-api-key"export CF_ZONE_ID="your-zone-id"
# 上传客户端CA证书curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/client_certificates" \ -H "X-Auth-Email: $CF_EMAIL" \ -H "X-Auth-Key: $CF_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----", "name": "My mTLS CA" }'
# 创建mTLS规则curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/firewall/rules" \ -H "X-Auth-Email: $CF_EMAIL" \ -H "X-Auth-Key: $CF_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "filter": { "expression": "(http.host eq \"example.com\" and http.request.uri.path contains \"/api\")" }, "action": "challenge", "products": ["waf"], "description": "Require client certificate for API" }'Cloudflare Workers中验证mTLS
addEventListener('fetch', event => { event.respondWith(handleRequest(event.request))})
async function handleRequest(request) { // 获取客户端证书信息 const tlsInfo = request.cf.tlsClientAuth
if (!tlsInfo || !tlsInfo.certPresented) { return new Response('Client certificate required', { status: 401 }) }
// 验证证书信息 if (tlsInfo.certVerified) { const certInfo = { subject: tlsInfo.certSubjectDN, issuer: tlsInfo.certIssuerDN, serial: tlsInfo.certSerial, notBefore: tlsInfo.certNotBefore, notAfter: tlsInfo.certNotAfter }
// 基于证书信息的访问控制 if (certInfo.subject.includes('CN=authorized-client')) { return new Response(JSON.stringify({ message: 'Access granted', clientInfo: certInfo }), { headers: { 'Content-Type': 'application/json' } }) } }
return new Response('Unauthorized', { status: 403 })}客户端配置
cURL测试
# 基本测试curl --cert client/client.crt \ --key client/client.key \ --cacert ca/ca.crt \ https://example.com
# 详细调试curl -v \ --cert client/client.crt \ --key client/client.key \ --cacert ca/ca.crt \ --resolve example.com:443:192.168.1.100 \ https://example.comPython客户端
import requestsimport ssl
# 配置客户端证书cert = ('client/client.crt', 'client/client.key')ca_cert = 'ca/ca.crt'
# 发送请求response = requests.get( 'https://example.com', cert=cert, verify=ca_cert)
print(f"Status: {response.status_code}")print(f"Response: {response.text}")
# 使用Session保持连接session = requests.Session()session.cert = certsession.verify = ca_cert
# 多个请求for i in range(5): resp = session.get(f'https://example.com/api/data/{i}') print(f"Request {i}: {resp.status_code}")Node.js客户端
const https = require('https');const fs = require('fs');
const options = { hostname: 'example.com', port: 443, path: '/', method: 'GET', key: fs.readFileSync('client/client.key'), cert: fs.readFileSync('client/client.crt'), ca: fs.readFileSync('ca/ca.crt'), rejectUnauthorized: true};
const req = https.request(options, (res) => { console.log(`Status: ${res.statusCode}`);
res.on('data', (d) => { process.stdout.write(d); });});
req.on('error', (e) => { console.error(e);});
req.end();浏览器配置
Chrome/Edge
# Windowscertutil -addstore -user Root ca/ca.crtcertutil -addstore -user My client/client.p12
# macOSsecurity add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain ca/ca.crtsecurity import client/client.p12 -k ~/Library/Keychains/login.keychain -P 123456
# Linuxpk12util -i client/client.p12 -d sql:$HOME/.pki/nssdb -W 123456certutil -A -n "MyCA" -t "TCu,Cu,Tu" -i ca/ca.crt -d sql:$HOME/.pki/nssdb故障排查
常见错误及解决方案
- 证书验证失败
# 检查证书链openssl verify -CAfile ca/ca.crt server/server.crtopenssl verify -CAfile ca/ca.crt client/client.crt
# 检查证书有效期openssl x509 -in client/client.crt -noout -dates- SSL握手失败
# 测试SSL连接openssl s_client -connect example.com:443 \ -cert client/client.crt \ -key client/client.key \ -CAfile ca/ca.crt \ -showcerts- 权限问题
# 设置正确的文件权限chmod 400 server/server.keychmod 400 client/client.keychmod 644 server/server.crtchmod 644 client/client.crtchmod 644 ca/ca.crt日志分析
Nginx日志
http { log_format mtls '$remote_addr - $ssl_client_s_dn [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_user_agent" $ssl_protocol/$ssl_cipher';
access_log /var/log/nginx/mtls_access.log mtls; error_log /var/log/nginx/mtls_error.log debug;}分析工具
# 监控证书过期#!/bin/bashfor cert in ~/mtls/**/*.crt; do echo "Checking $cert:" openssl x509 -in "$cert" -noout -enddatedone
# 自动续期脚本#!/bin/bashDAYS_BEFORE_EXPIRY=30CERT_FILE="server/server.crt"
EXPIRY_DATE=$(openssl x509 -in $CERT_FILE -noout -enddate | cut -d= -f2)EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)CURRENT_EPOCH=$(date +%s)DAYS_LEFT=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $DAYS_BEFORE_EXPIRY ]; then echo "Certificate expires in $DAYS_LEFT days. Renewing..." # 执行续期命令fi实践
监控和告警
// 监控脚本示例const checkCertificateExpiry = () => { const certPath = 'server/server.crt'; const cert = fs.readFileSync(certPath); const x509 = new crypto.X509Certificate(cert);
const expiryDate = new Date(x509.validTo); const daysUntilExpiry = Math.floor((expiryDate - Date.now()) / (1000 * 60 * 60 * 24));
if (daysUntilExpiry < 30) { // 发送告警 console.warn(`Certificate expires in ${daysUntilExpiry} days!`); }};
// 每天检查一次setInterval(checkCertificateExpiry, 24 * 60 * 60 * 1000);说在最后
我也不知道说什么最好中午弄,因为早晚会出事
The end Ciallo~
mTSL使用基本教程
https://fuwari.oh1.top/posts/Guide/mTSL/