尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

APISIX 证书(SSL/SNI)配置完全指南:从单域名到多 CA 证书实战

APISIX 证书(SSL/SNI)配置完全指南:从单域名到多 CA 证书实战 APISIX 证书SSL/SNI配置完全指南从单域名到多 CA 证书实战【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix导读本文基于 Apache APISIX 官方文档《Certificate》展开系统讲解云原生 API 网关 APISIX 基于 TLS 扩展 Server Name IndicationSNI加载多张 SSL 证书的完整机制。你将掌握单域名、通配符域名、多域名共享证书、单域名多证书ECC RSA 双算法以及多 CA 证书CA Bundle五类场景的配置方法与验证手段并深入理解 apisix/ssl/router/radixtree_sni.lua 中 SNI 匹配、证书加载与客户端校验的底层实现。阅读完成后你可以直接照着文中命令在本地 APISIX 环境复现全部场景。一、核心概念APISIX 的 SSL 对象与 SNI 匹配机制APISIX 支持通过 TLS 扩展 Server Name IndicationSNI在同一网关实例上加载多张 SSL 证书客户端在 TLS 握手的 ClientHello 中携带目标主机名APISIX 据此挑选对应的证书与私钥完成握手。从数据模型上看ssl是 APISIX 的核心资源之一通过 Admin API 的/apisix/admin/ssls端点管理。其字段结构定义在 apisix/schema_def.lua 中核心字段如下字段类型说明certstringPEM 编码的 SSL 公钥证书必填keystringPEM 编码的 SSL 私钥必填snistring与该证书关联的单个主机名SNIsnisarray与该证书关联的主机名数组元素个数至少为 1certsarrayPEM 编码的证书数组用于单域名多证书场景keysarrayPEM 编码的私钥数组与certs按下标一一配对typestring证书类型server默认或clientclientobject客户端双向 TLS 校验配置ca、depth、skip_mtls_uri_regexstatusinteger1 启用默认/ 0 禁用ssl_protocolsarray允许的 TLS 协议取值TLSv1.1/TLSv1.2/TLSv1.3注意 schema 的约束逻辑apisix/schema_def.lua当type server时必须满足{sni, key, cert}或{snis, key, cert}两组必填组合之一cert/key/certs/keys除了直接填写 PEM 内容还支持$secret://、$env://形式的密钥引用secret URI便于敏感信息托管。在运行时所有ssl对象会被汇总为 SNI 路由表。从 apisix/ssl/router/radixtree_sni.lua 的create_router可以看到APISIX 将每个证书的snis数组或单个sni逐条反转后构建成一棵 radix tree握手时把客户端上报的 SNI 反转后分发匹配——反转是为了把前缀匹配转化为 radix tree 擅长的后缀匹配从而高效支持通配符。证书对象则通过 apisix/core/config_etcd.lua 从 etcd或 YAML 配置中心同步并在 apisix/init.lua 的http_ssl_client_hello_phase/http_ssl_phase两个阶段挂入 Nginx SSL 握手流程。二、单域名证书Single SNI最常见的场景一张证书只包含一个域名。需要创建ssl对象与route对象各一个。2.1 准备 Admin API 密钥Admin API 默认监听127.0.0.1:9180请求需携带X-API-KEY头。可以从 conf/config.yaml 中读取密钥并写入环境变量admin_key$(yq .deployment.admin.admin_key[0].key conf/config.yaml | sed s///g)2.2 创建 SSL 对象cert、key分别填入 PEM 编码的公钥证书与私钥snis指定证书关联的主机名。这里直接复用仓库自带的测试证书 t/certs/apisix.crt 与 t/certs/apisix.key其 Subject 的 CN 即test.com见 t/certs/openssl.confcurl http://127.0.0.1:9180/apisix/admin/ssls/1 \ -H X-API-KEY: $admin_key -X PUT -d { cert : $(cat t/certs/apisix.crt), key: $(cat t/certs/apisix.key), snis: [test.com] }2.3 创建路由对象路由的hosts必须与 SNI 保持一致才能让该域名的请求命中对应上游curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -i -d { uri: /get, hosts: [test.com], methods: [GET], upstream: { type: roundrobin, nodes: { httpbin.org: 1 } } }2.4 验证 TLS 握手用curl --resolve将test.com强制解析到本机访问 APISIX 的 HTTPS 端口9443-k跳过自签名证书校验curl --resolve test.com:9443:127.0.0.1 https://test.com:9443/get -k -vvv预期的关键输出* Added test.com:9443:127.0.0.1 to DNS cache * Connected to test.com (127.0.0.1) port 9443 (#0) * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * ALPN, server accepted to use h2 * Server certificate: * subject: CCN; STGuangDong; LZhuHai; Oiresty; CNtest.com * start date: Jun 24 22:18:05 2019 GMT * expire date: May 31 22:18:05 2119 GMT * SSL certificate verify result: self-signed certificate (18), continuing anyway. GET /get HTTP/2 Host: test.com:9443看到SSL connection using TLSv1.3与证书 subject 中的CNtest.com即说明 SNI 匹配成功。三、通配符域名证书Wildcard SNI一张证书可能对*.test.com这样的通配符域名有效即同时覆盖www.test.com、mail.test.com等任意子域。3.1 创建 SSL 对象只需把snis改为通配符形式curl http://127.0.0.1:9180/apisix/admin/ssls/1 \ -H X-API-KEY: $admin_key -X PUT -d { cert : $(cat t/certs/apisix.crt), key: $(cat t/certs/apisix.key), snis: [*.test.com] }3.2 创建路由对象路由的hosts同样使用通配符curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -i -d { uri: /get, hosts: [*.test.com], methods: [GET], upstream: { type: roundrobin, nodes: { httpbin.org: 1 } } }3.3 验证通配符匹配用www.test.com访问验证curl --resolve www.test.com:9443:127.0.0.1 https://www.test.com:9443/get -k -vvv关键输出* Added www.test.com:9443:127.0.0.1 to DNS cache * Connected to www.test.com (127.0.0.1) port 9443 (#0) * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * Server certificate: * subject: CCN; STGuangDong; LZhuHai; Oiresty; CNtest.com GET /get HTTP/2 Host: www.test.com:9443注意握手时 APISIX 为www.test.com匹配了*.test.com的证书证书 CN 为test.com说明通配符 SNI 匹配生效。从源码看通配符的精确匹配逻辑位于 apisix/ssl/router/radixtree_sni.luaradix tree 分发命中后还会对sni_rev与matched_sni做二次校验——对于*.test.com这类通配项要求访问的域名在test.com的子域层级之内通过判断sni_rev在#msni位置之后不再出现.分隔符实现避免fake.test.com.evil.com之类的域名误匹配到通配证书。仓库测试 t/admin/ssl.t 也覆盖了*.foo.com与[*.foo.com, bar.com]两种通配/混合配置的校验场景。四、多域名共享一张证书如果一张证书同时包含多个域名如www.test.com与mail.test.com将它们全部放入snis数组即可{ snis: [www.test.com, mail.test.com] }APISIX 会把数组中的每个域名都注册进 SNI 路由表apisix/ssl/router/radixtree_sni.lua 逐条反转后作为路由 path因此任一域名发起握手都能命中这张证书。schema 中snis的minItems 1保证了数组至少包含一个元素apisix/schema_def.lua。五、单域名多证书ECC 与 RSA 双算法并存当一个域名需要同时支持 ECC 与 RSA 两种密钥交换算法时例如老客户端只支持 RSA新客户端偏好 ECC可以为该域名配置多套证书。做法是第一套证书与私钥仍放在cert和key中额外的证书与私钥放入certs和keys数组。certsPEM 编码的证书数组keysPEM 编码的私钥数组。APISIX 会按下标一一配对证书与私钥组成 SSL 密钥对因此certs与keys的长度必须相等——这一约束在 apisix/ssl.lua 的check_ssl_conf中被显式校验numcerts ~ numkeys时直接返回mismatched number of certs and keys。下发证书时apisix/ssl/router/radixtree_sni.lua 的set_cert_and_key会先设置cert/key主密钥对再遍历certs数组、按下标取出keys[i]依次调用set_pem_ssl_key完成加载TLS 协商时会由 OpenSSL 根据客户端算法偏好自动选择合适的一张。六、多 CA 证书场景使用 CA Bundle 避免配置互相覆盖APISIX 在多处会使用 CA 证书例如保护 Admin APImTLS详见 文档mTLSAPISIX 与 etcd 之间的 mTLS各种部署模式详见 文档Deployment Modes。在这些位置分别通过ssl_trusted_certificate或trusted_ca_cert配置 CA 证书但这些配置最终都会被翻译为 OpenResty 的lua_ssl_trusted_certificate指令见 apisix/cli/ngx_tpl.lua 的 Nginx 模板渲染。如果不同位置配置了不同的 CA 证书生成的lua_ssl_trusted_certificate可能出现多个路径、互相覆盖。解决办法把多个 CA 证书打包成一个 CA Bundle 文件在所有需要 CA 的位置统一指向该文件。6.1 场景设定假设客户端与 APISIX Admin API、APISIX 与 ETCD 之间全部使用 mTLS 通信且存在两张 CAfoo_ca.crt签发了客户端访问 Admin API 所需的次级证书bar_ca.crt签发了 APISIX 与 ETCD 通信所需的次级证书。涉及的全部配置项如下表配置类型作用foo_ca.crtCA cert签发客户端与 APISIX Admin API 进行 mTLS 通信所需的次级证书foo_client.crtcert由foo_ca.crt签发客户端访问 Admin API 时证明身份foo_client.keykey由foo_ca.crt签发客户端访问 Admin API 所需的密钥文件foo_server.crtcert由foo_ca.crt签发供 APISIX 使用对应admin_api_mtls.admin_ssl_cert配置项foo_server.keykey由foo_ca.crt签发供 APISIX 使用对应admin_api_mtls.admin_ssl_cert_key配置项admin.apisix.dev域名签发foo_server.crt时使用的 Common Name客户端通过它访问 Admin APIbar_ca.crtCA cert签发 APISIX 与 ETCD 进行 mTLS 通信所需的次级证书bar_etcd.crtcert由bar_ca.crt签发供 ETCD 使用对应 ETCD 启动命令中的-cert-file选项bar_etcd.keykey由bar_ca.crt签发供 ETCD 使用对应 ETCD 启动命令中的--key-file选项bar_apisix.crtcert由bar_ca.crt签发供 APISIX 使用对应etcd.tls.cert配置项bar_apisix.keykey由bar_ca.crt签发供 APISIX 使用对应etcd.tls.key配置项etcd.cluster.dev域名签发bar_etcd.crt时使用的 Common Name作为 APISIX 与 ETCD mTLS 通信时的 SNI对应etcd.tls.sni配置项apisix.ca-bundleCA bundle由foo_ca.crt与bar_ca.crt合并而成替换二者被引用的位置6.2 第一步创建 CA Bundle 文件cat /path/to/foo_ca.crt /path/to/bar_ca.crt apisix.ca-bundle6.3 第二步启动启用客户端认证的 ETCD 集群编写goreman配置文件Procfile-single-enable-mtls需先执行go get github.com/mattn/goreman安装 goreman# Use goreman to run go get github.com/mattn/goreman etcd1: etcd --name infra1 --listen-client-urls https://127.0.0.1:12379 --advertise-client-urls https://127.0.0.1:12379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12380 --initial-cluster-token etcd-cluster-1 --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 --initial-cluster-state new --cert-file /path/to/bar_etcd.crt --key-file /path/to/bar_etcd.key --client-cert-auth --trusted-ca-file /path/to/apisix.ca-bundle etcd2: etcd --name infra2 --listen-client-urls https://127.0.0.1:22379 --advertise-client-urls https://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 --initial-advertise-peer-urls http://127.0.0.1:22380 --initial-cluster-token etcd-cluster-1 --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 --initial-cluster-state new --cert-file /path/to/bar_etcd.crt --key-file /path/to/bar_etcd.key --client-cert-auth --trusted-ca-file /path/to/apisix.ca-bundle etcd3: etcd --name infra3 --listen-client-urls https://127.0.0.1:32379 --advertise-client-urls https://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 --initial-advertise-peer-urls http://127.0.0.1:32380 --initial-cluster-token etcd-cluster-1 --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 --initial-cluster-state new --cert-file /path/to/bar_etcd.crt --key-file /path/to/bar_etcd.key --client-cert-auth --trusted-ca-file /path/to/apisix.ca-bundle三个节点全部启用--client-cert-auth要求客户端提供证书并把apisix.ca-bundle作为--trusted-ca-file。随后后台启动goreman -f Procfile-single-enable-mtls start goreman.log 21 6.4 第三步更新 config.yaml在 conf/config.yaml 中按如下结构配置原文档示例中admin_key后缺少冒号此处为可运行的修正写法deployment: admin: admin_key: - name: admin key: edd1c9f034335f136f87ad84b625c8f1 role: admin admin_listen: ip: 127.0.0.1 port: 9180 https_admin: true admin_api_mtls: admin_ssl_ca_cert: /path/to/apisix.ca-bundle admin_ssl_cert: /path/to/foo_server.crt admin_ssl_cert_key: /path/to/foo_server.key apisix: ssl: ssl_trusted_certificate: /path/to/apisix.ca-bundle deployment: role: traditional role_traditional: config_provider: etcd etcd: host: - https://127.0.0.1:12379 - https://127.0.0.1:22379 - https://127.0.0.1:32379 tls: cert: /path/to/bar_apisix.crt key: /path/to/bar_apisix.key sni: etcd.cluster.dev关键点解析admin_api_mtls.admin_ssl_ca_cert指向 CA Bundle用于校验访问 Admin API 的客户端证书apisix.ssl.ssl_trusted_certificate指向同一个 CA Bundle。该配置项在源码中被多处复用渲染 Nginx 时生成lua_ssl_trusted_certificate指令apisix/cli/ngx_tpl.lua同时作为 etcd 客户端的trusted_caapisix/core/etcd.lua以及ssl.wrap的cafileapisix/patch.lua。正是由于这些位置统一指向同一个 Bundle 文件lua_ssl_trusted_certificate只生成一个路径避免了互相覆盖etcd.tls.sni: etcd.cluster.dev是 APISIX 连接 ETCD 时 TLS 握手中的 SNI启动时 apisix/cli/ops.lua 会校验ssl_trusted_certificate指向的文件是否存在并转换为绝对路径因此请确保路径真实有效。6.5 第四步测试 APISIX Admin API启动 APISIX 后若logs/error.log中无异常输出说明 APISIX 与 ETCD 之间的 mTLS 通信正常。接着用 curl 模拟客户端以 mTLS 方式访问 Admin API 并创建路由curl -vvv \ --resolve admin.apisix.dev:9180:127.0.0.1 https://admin.apisix.dev:9180/apisix/admin/routes/1 \ --cert /path/to/foo_client.crt \ --key /path/to/foo_client.key \ --cacert /path/to/apisix.ca-bundle \ -H X-API-KEY: $admin_key -X PUT -i -d { uri: /get, upstream: { type: roundrobin, nodes: { httpbin.org:80: 1 } } }若 curl 与 Admin API 之间的 mTLS 握手成功会看到如下 TLSv1.3 双向认证的握手流程包含客户端证书发送Certificate (11)与CERT verify (15)两个阶段* TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Request CERT (13): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Certificate (11): * TLSv1.3 (OUT), TLS handshake, CERT verify (15): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA3846.6 第五步验证 APISIX 代理链路通过普通 HTTP 访问代理端口9080确认路由已生效、请求被转发到上游curl http://127.0.0.1:9080/get -i预期输出HTTP/1.1 200 OK Content-Type: application/json Content-Length: 298 Connection: keep-alive Date: Tue, 26 Jul 2022 16:31:00 GMT Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Server: APISIX/2.14.1 ...APISIX 将请求代理到上游httpbin.org的/get路径并返回HTTP/1.1 200 OK。至此使用 CA Bundle 替代单张 CA 证书的完整链路Admin API mTLS etcd mTLS 代理转发验证通过。七、底层实现要点证书加载与客户端校验7.1 SNI 获取与证书解析缓存apisix/ssl.lua 的server_name从 ClientHello 中提取 SNIngx_ssl_client.get_client_hello_server_name未携带 SNI 时可回退到apisix.ssl.fallback_sni配置随后会去掉末尾点并转为小写保证TEST.COM.与test.com能归一化匹配。证书与私钥的 PEM 解析结果通过两级 LRU 缓存cert_cache与pkey_cacheTTL 3600 秒、容量 1024见 apisix/ssl.lua复用避免每个 TLS 连接都重新解析 PEM。此外validate函数apisix/ssl.lua会调用ngx_ssl.parse_pem_cert/ngx_ssl.parse_pem_priv_key校验证书与私钥的合法性私钥若已加密支持apisix.data_encryption.keyring配置会先解密再解析。7.2 ClientHello 阶段的协议与证书下发在 apisix/init.lua 的http_ssl_client_hello_phase中APISIX 提前从 ClientHello 拿到 SNI执行router_ssl.match_and_set匹配证书对象并据ssl_protocols字段调用apisix_ssl.set_protocols_by_clienthello限制该 SNI 允许的 TLS 版本TLSv1.1/TLSv1.2/TLSv1.3见 apisix/schema_def.lua。正式握手阶段http_ssl_phase再真正下发证书。7.3 客户端证书校验mTLS在 apisix/ssl/router/radixtree_sni.lua 的set中若 SSL 对象配置了client字段APISIX 会解析client.ca指定的 CA 证书调用ngx_ssl.verify_client(ca_cert, depth, reject_in_handshake)要求客户端提供证书depth默认 1表示客户端证书链的最大校验深度skip_mtls_uri_regex可配置跳过 mTLS 校验的 URI 正则列表此时改为在请求阶段按 URI 决定是否强制校验流式子系统中则始终在握手阶段拒绝未通过校验的客户端。该能力对 upstream 方向的客户端证书type client同样适用schema 中type的枚举即为server/clientapisix/schema_def.lua。八、常见问题与注意事项证书与私钥必须配对单证书场景cert/key缺一不可多证书场景certs与keys长度必须一致否则 Admin API 返回mismatched number of certs and keys。SNI 与路由hosts保持一致证书匹配与路由匹配是两个独立过程只有hosts覆盖了 SNI 域名TLS 握手成功后才能命中正确的上游。通配符不能跨层级误配*.test.com不会匹配test.com.evil.comAPISIX 的二次校验逻辑已杜绝此类穿透。多 CA 场景务必使用 CA Bundle分散配置多个ssl_trusted_certificate会导致生成的lua_ssl_trusted_certificate指令相互覆盖统一合并为单个 Bundle 文件并在 conf/config.yaml 的apisix.ssl.ssl_trusted_certificate与admin_api_mtls.admin_ssl_ca_cert处引用同一路径即可规避。路径有效性检查ssl_trusted_certificate指向的文件在 APISIX 启动时会被校验相对路径按当前工作目录解析后转为绝对路径路径不存在将导致启动失败详见 apisix/cli/ops.lua。测试证书仅用于本地验证仓库 t/certs/ 下的apisix.crt/apisix.key为自签名测试证书有效期至 2119 年生产环境请替换为受信任 CA 签发的正式证书。通过本文的五类场景与源码剖析你可以完整掌握 APISIX 的 SSL/SNI 配置体系从最基本的单域名证书下发到通配符与多域名共享再到 ECC/RSA 双证书并存以及最复杂的多 CA mTLS 链路。更深入的 mTLS 细节如 Admin API 保护、etcd mTLS可继续阅读 mTLS 文档 与 部署模式文档。【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表