证书
SNI(Server Name Indication)是用来改善 SSL 和 TLS 的一项特性,它允许客户端在服务器端向其发送证书之前向服务器端发送请求的域名,服务器端根据客户端请求的域名选择合适的 SSL 证书发送给客户端。
通常情况下一个 SSL 证书只包含一个静态域名,配置一个 ssl
参数对象,它包括 cert
、key
和sni
三个属性,详细如下:
cert
:SSL 密钥对的公钥,pem 格式key
:SSL 密钥对的私钥,pem 格式snis
:SSL 证书所指定的一个或多个域名,注意在设置这个参数之前,你需要确保这个证书对应的私钥是有效的。
为了简化示例,我们会使用下面的 Python 脚本:
create-ssl.py
# 创建 SSL 对象
./create-ssl.py t.crt t.key test.com
# 创建 Router 对象
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"uri": "/hello",
"hosts": ["test.com"],
"methods": ["GET"],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
# 测试一下
curl --resolve 'test.com:9443:127.0.0.1' https://test.com:9443/hello -vvv
* Added test.com:9443:127.0.0.1 to DNS cache
* About to connect() to test.com port 9443 (#0)
* Trying 127.0.0.1...
* Connected to test.com (127.0.0.1) port 9443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* subject: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
* start date: Jun 24 22:18:05 2019 GMT
* expire date: May 31 22:18:05 2119 GMT
* common name: test.com
* issuer: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
> GET /hello HTTP/1.1
> User-Agent: curl/7.29.0
> Host: test.com:9443
> Accept: */*
一个 SSL 证书的域名也可能包含泛域名,如 *.test.com
,它代表所有以 test.com
结尾的域名都可以使用该证书。 比如 *.test.com
,可以匹配 www.test.com
、mail.test.com
。
看下面这个例子,请注意我们把 *.test.com
作为 sni 传递进来:
./create-ssl.py t.crt t.key '*.test.com'
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"hosts": ["*.test.com"],
"methods": ["GET"],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
curl --resolve 'www.test.com:9443:127.0.0.1' https://www.test.com:9443/hello -vvv
* Added test.com:9443:127.0.0.1 to DNS cache
* About to connect() to test.com port 9443 (#0)
* Trying 127.0.0.1...
* Connected to test.com (127.0.0.1) port 9443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* subject: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
* start date: Jun 24 22:18:05 2019 GMT
* expire date: May 31 22:18:05 2119 GMT
* common name: test.com
* issuer: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
> GET /hello HTTP/1.1
> User-Agent: curl/7.29.0
> Host: test.com:9443
> Accept: */*
如果你期望为一个域名配置多张证书,例如以此来同时支持使用 ECC 和 RSA 的密钥交换算法,那么你可以将额外的证书和私钥(第一张证书和其私钥依然使用 cert
和 key
)配置在 certs
和 keys
中。
certs
:PEM 格式的 SSL 证书列表keys
:PEM 格式的 SSL 证书私钥列表
APISIX
会将相同下标的证书和私钥配对使用,因此 certs
和 keys
列表的长度必须一致。
APISIX 目前支持在多处设置 CA 证书,比如 等。
在这些地方,使用 ssl_trusted_certificate
或 trusted_ca_cert
来配置 CA 证书,但是这些配置最终将转化为 OpenResty 的 lua_ssl_trusted_certificate 指令。
如果你需要在不同的地方指定不同的 CA 证书,你可以将这些 CA 证书制作成一个 CA bundle 文件,在需要用到 CA 证书的地方将配置指向这个文件。这样可以避免生成的 lua_ssl_trusted_certificate
存在多处并且互相覆盖的问题。
下面用一个完整的例子来展示如何在 APISIX 设置多个 CA 证书。
下表详细列出这个示例所涉及到的配置及其作用:
- 制作 CA bundle 文件
cat /path/to/foo_ca.crt /path/to/bar_ca.crt > apisix.ca-bundle
- 启动 ETCD 集群,并开启客户端验证
先编写 goreman
配置,命名为 Procfile-single-enable-mtls
,内容如下:
# 运行 `go get github.com/mattn/goreman` 安装 goreman,用 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 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://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 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://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
使用 goreman
来启动 ETCD 集群:
- 更新
config.yaml
conf/config.yaml
deployment:
admin:
admin_key
- name: admin
key: edd1c9f034335f136f87ad84b625c8f1
role: admin
admin_listen:
ip: 127.0.0.1
port: 9180
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
启动 APISIX,如果 APISIX 启动成功,logs/error.log
中没有异常输出,表示 APISIX 与 ETCD 之间进行 mTLS 通信正常。
用 curl 模拟客户端,与 APISIX Admin API 进行 mTLS 通信,并创建一条路由:
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: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"uri": "/get",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
- 验证 APISIX 代理
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 证书是正常可用的。