目录

  1. 零信任安全与双向 TLS (mTLS) 概述
  2. 环境准备:CA 证书与 K8s Secret 创建
  3. 方案实战 A:RKE2 默认 Traefik Ingress 配置
  4. 方案实战 B:Nginx Ingress Controller 配置
  5. 用户侧:客户端证书生成与 PKCS#12 (.p12) 打包
  6. 客户端授信导入与多终端测试
  7. 生产级故障排查与最佳实践 (Troubleshooting)

1. 零信任安全与双向 TLS (mTLS) 概述

1.1 什么是双向 TLS(mTLS)?

在传统的**单向 TLS(One-way TLS)**中,只有服务端(网站)需要出示证书来向客户端(浏览器)证明自己是真实合法的,客户端校验证书通过后建立加密通道。

而在**双向 TLS(mTLS / Mutual TLS)**中,除了上述步骤,服务端也会强制要求客户端出示身份证书。只有当双方证书都经过彼此信任的 CA 机构验证无误后,加密通道才能成功建立。

K8s Ingress 网关 (Traefik/Nginx) 客户端 (浏览器/设备) K8s Ingress 网关 (Traefik/Nginx) 客户端 (浏览器/设备) 第一阶段:服务端身份验证 (单向 TLS) 第二阶段:客户端身份验证 (双向验证开始) 发起 Client Hello (携带支持的密码套件) 1 Server Hello & 发送服务端证书 (server.crt) 2 校验证书链 (是否由可信 CA 签发) 3 发起 Certificate Request (请求客户端证书) 4 发送客户端证书 (client.crt) & Certificate Verify 5 用内部 CA (ca.crt) 解密并验证客户端签名 6 完成握手,建立安全的 mTLS 双向加密通道 7

1.2 mTLS 的核心优势与应用场景

  • 绝对防绕过:单向自签证书中,用户可以通过在浏览器点击“高级 -> 继续访问”强行绕过证书警告;但在 mTLS 握手阶段,如果客户端证书缺失或无效,Ingress 会在 TCP 握手层面直接强行中断连接,完全没有绕过机制,安全性达到 100%。
  • 物理身份隔离:不依赖用户的用户名和密码。证书保存在设备的安全区域中,极大地防范了钓鱼攻击与凭证泄露。
  • 主要应用场景:企业核心敏感资产(财务、内网管理后台)、微服务间通信(如 Istio 等服务网格)、物联网(IoT)智能设备安全通信接入、银行金融级的 B2B API 对接。

2. 环境准备:CA 证书与 K8s Secret 创建

无论使用 Traefik 还是 Nginx Ingress,服务器都必须加载自建 CA 的公钥证书**,用于对来访的客户端证书进行数字签名解密与合法性确认。

[!CAUTION]
安全防护:在任何 Kubernetes 部署场景下,我们都只需要上传 CA 的公钥证书(ca.crt
绝对不要将 CA 的私钥(ca.key)上传至集群中。私钥应始终离线妥善保存在安全的物理媒介或硬件加密钥匙箱(HSM)中。

步骤 2.1:在 Kubernetes 中创建 CA 证书的 Secret

在业务应用所在的 Namespace(如 my-namespace)中执行以下命令,将 CA 公钥证书转化为 Kubernetes Secret:

kubectl create secret generic mtls-ca-secret \
  --from-file=tls.ca=ca.crt \
  -n <your-namespace>
  • 注意:命令中 --from-file=tls.ca=ca.crt 明确将 Secret 内部的键(Key)定义为 tls.ca。Traefik 及 Nginx 控制器会自动识别并提取该键下的内容作为受信任的根 CA 列表。

3. 方案 A:RKE2 默认 Traefik Ingress 配置

步骤 3.1:定义并部署 TLSOption 资源

创建一个名为 tls-option.yaml 的清单文件。请确保 TLSOptionnamespace 与上一步创建的 CA Secret 保持一致:

apiVersion: traefik.io/v1alpha1
kind: TLSOption
metadata:
  name: mtls-option
  namespace: <your-namespace>
spec:
  clientAuth:
    secretNames:
      - mtls-ca-secret # 引用步骤 2.1 创建的包含 CA 公钥的 Secret
    clientAuthType: RequireAndVerifyClientCert # 强制要求并验证客户端证书
  • 参数释义
    • RequireAndVerifyClientCert:这是最安全的模式。要求连接请求中必须包含客户端证书,且该证书必须由 secretNames 列表中的 CA 签名,否则直接阻断连接并抛出 HTTP 400 Bad Request

部署资源:

kubectl apply -f tls-option.yaml

步骤 3.2:配置 Ingress 关联 TLSOption

对于标准的 Kubernetes Ingress 资源,需要通过特定注解挂载 Traefik 的 TLSOption

[!WARNING]
引用命名空间的重要陷阱
在 Kubernetes Ingress 注解中引用 Traefik CRD 资源时,其值必须遵循独特的跨命名空间命名规范,语法为:命名空间-资源名称@kubernetescrd
假设命名空间为 my-namespace,资源名称为 mtls-option,则注解的配置值必须为 my-namespace-mtls-option@kubernetescrd,否则 Traefik 将无法在路由树上加载对应的 TLS 设置!

Ingress YAML 清单文件:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-traefik-app-ingress
  namespace: <your-namespace>
  annotations:
    kubernetes.io/ingress.class: traefik
    # 核心关联注解:绑定 Traefik 的双向认证规则
    traefik.ingress.kubernetes.io/router.tls.options: <your-namespace>-mtls-option@kubernetescrd
spec:
  tls:
    - hosts:
        - your-website.com
      secretName: your-server-cert # 网站自身的域名 HTTPS 证书 (由公网授信 CA 签发)
  rules:
    - host: your-website.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: your-service-name
                port:
                  number: 80

4. 方案实战 B:Nginx Ingress Controller 配置

如果集群为了兼容性改用了社区版 Nginx Ingress Controller (ingress-nginx),其实现 mTLS 显得更加轻量化——无需定义复杂的自定义 CRD,直接在 Ingress 的元数据注解(Annotations)中做一步式声明即可。

步骤 4.1:创建 Nginx CA Secret

Nginx 要求 CA 证书在 Secret 中的 key 最好为 ca.crt。请执行以下命令:

kubectl create secret generic nginx-mtls-ca \
  --from-file=ca.crt=ca.crt \
  -n <your-namespace>

步骤 4.2:在 Ingress 资源中直接添加配置注解

使用以下三个核心注解控制 Nginx 的双向认证行为:

  • nginx.ingress.kubernetes.io/auth-tls-verify-client:设为 "on" 以在 TLS 握手层启用强制客户端认证。
  • nginx.ingress.kubernetes.io/auth-tls-secret:指向信任的 CA 证书,格式必须是 "命名空间/Secret名称"(例如 my-namespace/nginx-mtls-ca)。
  • nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream:设为 "true",Nginx 会将完整的 PEM 格式客户端证书通过 HTTP Header(X-ARR-ClientCertssl-client-cert)透传给后端的业务容器。
Ingress YAML 清单文件:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-nginx-app-ingress
  namespace: <your-namespace>
  annotations:
    kubernetes.io/ingress.class: nginx
    # 1. 启动客户端 TLS 双向校验
    nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
    # 2. 绑定 CA Secret
    nginx.ingress.kubernetes.io/auth-tls-secret: "<your-namespace>/nginx-mtls-ca"
    # 3. 透传证书信息,供后端业务程序实现应用层 RBAC 细粒度审计/认证
    nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
spec:
  tls:
    - hosts:
        - your-website.com
      secretName: your-server-cert # 服务端 HTTPS 证书
  rules:
    - host: your-website.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: your-service-name
                port:
                  number: 80

5. 用户侧:客户端证书生成与 PKCS#12 (.p12) 打包

服务器端配置就绪后,直接访问该网站应当会收到 HTTP 400 握手错误。现在,我们需要利用 CA 私钥在本地生成并签发一张合法的客户端证书。

client.key 私钥

client.csr 签名请求

ca.key & ca.crt

client.crt 客户端公钥

client.p12 加密包

步骤 5.1:生成客户端私钥与证书签名请求 (CSR)

在本地主机的终端中执行 OpenSSL 命令,生成非对称密码对以及证书属性申请文件:

# 1. 生成高强度客户端私钥 (client.key)
openssl genrsa -out client.key 2048

# 2. 生成证书签名请求文件 (client.csr)
# 这里的 Common Name (CN) 建议设定为可读性强的具体用户名,例如 client-user-01
openssl req -new -key client.key -subj "/CN=client-user-01" -out client.csr

步骤 5.2:使用 CA 签名生成客户端公钥证书 (client.crt)

扮演 CA 的角色,使用离线持有的 CA 私钥(ca.key)签署客户端请求,生成拥有过期时效的有效客户端证书:

openssl x509 -req \
  -in client.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out client.crt \
  -days 365
  • 执行完毕后,当前目录下会生成 client.crt 文件。

步骤 5.3:将证书打包为 PKCS#12 (.p12) 二进制格式

大部分移动设备和操作系统(如 Windows/macOS/Chrome/Safari)为了防篡改和数据完整性,无法直接接收散开的私钥和证书。必须使用 PKCS#12 标准将其整合打包为一个包含证书链和私钥的单一文件。

openssl pkcs12 -export \
  -out client.p12 \
  -inkey client.key \
  -in client.crt \
  -certfile ca.crt

[!IMPORTANT]
警告:该命令在运行期间会强制要求设定一个 “导出密码(Export Password)”
请妥善记录此密码,客户端用户在将 .p12 证书导入个人 PC 或手机操作系统时,必须输入此密码才可以解开打包文件进行安装。


6. 客户端授信导入与多终端测试

6.1 导入操作系统/浏览器

  • macOS 系统:直接双击 client.p12 文件,系统会唤起 “钥匙串访问(Keychain Access)”。将证书导入至 “登录 (login)” 钥匙串,并输入导出密码。
  • Windows 系统:双击 client.p12 文件,根据证书导入向导,将其部署至“当前用户”下的 “个人” 证书存储区。

6.2 浏览器访问测试

  1. 彻底关闭浏览器以清空历史 TLS Session 缓存(也可以直接打开“隐身/无痕”标签页)。
  2. 访问配置了 mTLS 的域名:https://your-website.com
  3. 此时,浏览器会自动探测到服务端的 Certificate Request,并在屏幕上弹窗询问您要提交哪张证书进行校验。
  4. 选中刚刚导入的 client-user-01 证书,确认后即可绕过 400 阻断,顺利进入系统!

7. 生产级故障排查与最佳实践 (Troubleshooting)

在实际的生产环境中,mTLS 很容易由于各种配置细节或缓存导致握手失败。

7.1 使用 openssl 命令行深度调试

当浏览器访问报错且无法判断原因时,使用以下命令能够看到最底层的 TLS 握手细节,帮助诊断证书是否有效:

openssl s_client -connect your-website.com:443 \
  -cert client.crt \
  -key client.key \
  -CAfile ca.crt \
  -debug
  • 如果连接成功:您将在最后看到终端打印出 HTTP 的调试数据。
  • 如果服务端配置有误s_client 会具体抛出是哪一方的验证链(Verification error)断裂。

7.2 踩坑排查一览表

故障现象 可能原因 解决办法
访问直接返回 HTTP 400 Bad Request 客户端没有提供证书,或者提供的证书已经过期、无效。 1. 确保 .p12 文件已正确安装。
2. 重新关闭并彻底重启浏览器,清除已缓存的空 TLS 握手状态。
3. 检查客户端证书的过期时间是否满足 -days 限制。
导入证书后依然没有弹窗提示选择证书 证书的 Common Name (CN) 或者别名不符合规则,或浏览器没有正确读取系统钥匙串。 1. 用 openssl x509 -in client.crt -noout -text 检查 CN 是否正确。
2. 部分浏览器(如 Firefox)使用自己独立的证书管理器,需在设置中手动导入。
Ingress 报错:Secret not found 或没有任何拦截效果 [最易踩坑] 命名空间或注解格式不正确。 1. Traefik:确认注解是否包含了命名空间前缀,即 <namespace>-<name>@kubernetescrd
2. Nginx:确认注解 auth-tls-secret 的值是否是 "namespace/name" 格式,且 Secret 内部的文件名 key 命名为 ca.crt
服务器端证书报错 Certificate expired K8s 内部 CA 证书签名过期。 1. 确认 RKE2 服务端或您自建 CA 的寿命。
2. 如果更换了 CA,必须执行 kubectl apply 重新覆盖上传 CA Secret。
Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐