证书

    SNI(Server Name Indication)是用来改善 SSL 和 TLS 的一项特性,它允许客户端在服务器端向其发送证书之前向服务器端发送请求的域名,服务器端根据客户端请求的域名选择合适的 SSL 证书发送给客户端。

    通常情况下一个 SSL 证书只包含一个静态域名,配置一个 ssl 参数对象,它包括 certkeysni三个属性,详细如下:

    • cert:SSL 密钥对的公钥,pem 格式
    • key:SSL 密钥对的私钥,pem 格式
    • snis:SSL 证书所指定的一个或多个域名,注意在设置这个参数之前,你需要确保这个证书对应的私钥是有效的。

    为了简化示例,我们会使用下面的 Python 脚本:

    create-ssl.py

    1. # 创建 SSL 对象
    2. ./create-ssl.py t.crt t.key test.com
    3. # 创建 Router 对象
    4. curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
    5. {
    6. "uri": "/hello",
    7. "hosts": ["test.com"],
    8. "methods": ["GET"],
    9. "upstream": {
    10. "type": "roundrobin",
    11. "nodes": {
    12. "127.0.0.1:1980": 1
    13. }
    14. }
    15. }'
    16. # 测试一下
    17. curl --resolve 'test.com:9443:127.0.0.1' https://test.com:9443/hello -vvv
    18. * Added test.com:9443:127.0.0.1 to DNS cache
    19. * About to connect() to test.com port 9443 (#0)
    20. * Trying 127.0.0.1...
    21. * Connected to test.com (127.0.0.1) port 9443 (#0)
    22. * Initializing NSS with certpath: sql:/etc/pki/nssdb
    23. * skipping SSL peer certificate verification
    24. * SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    25. * Server certificate:
    26. * subject: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
    27. * start date: Jun 24 22:18:05 2019 GMT
    28. * expire date: May 31 22:18:05 2119 GMT
    29. * common name: test.com
    30. * issuer: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
    31. > GET /hello HTTP/1.1
    32. > User-Agent: curl/7.29.0
    33. > Host: test.com:9443
    34. > Accept: */*

    一个 SSL 证书的域名也可能包含泛域名,如 *.test.com,它代表所有以 test.com 结尾的域名都可以使用该证书。 比如 *.test.com,可以匹配 www.test.commail.test.com

    看下面这个例子,请注意我们把 *.test.com 作为 sni 传递进来:

    1. ./create-ssl.py t.crt t.key '*.test.com'
    2. curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
    3. {
    4. "hosts": ["*.test.com"],
    5. "methods": ["GET"],
    6. "upstream": {
    7. "type": "roundrobin",
    8. "nodes": {
    9. "127.0.0.1:1980": 1
    10. }
    11. }
    12. }'
    13. curl --resolve 'www.test.com:9443:127.0.0.1' https://www.test.com:9443/hello -vvv
    14. * Added test.com:9443:127.0.0.1 to DNS cache
    15. * About to connect() to test.com port 9443 (#0)
    16. * Trying 127.0.0.1...
    17. * Connected to test.com (127.0.0.1) port 9443 (#0)
    18. * Initializing NSS with certpath: sql:/etc/pki/nssdb
    19. * skipping SSL peer certificate verification
    20. * SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    21. * Server certificate:
    22. * subject: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
    23. * start date: Jun 24 22:18:05 2019 GMT
    24. * expire date: May 31 22:18:05 2119 GMT
    25. * common name: test.com
    26. * issuer: CN=test.com,O=iresty,L=ZhuHai,ST=GuangDong,C=CN
    27. > GET /hello HTTP/1.1
    28. > User-Agent: curl/7.29.0
    29. > Host: test.com:9443
    30. > Accept: */*

    如果你期望为一个域名配置多张证书,例如以此来同时支持使用 ECC 和 RSA 的密钥交换算法,那么你可以将额外的证书和私钥(第一张证书和其私钥依然使用 certkey)配置在 certskeys 中。

    • certs:PEM 格式的 SSL 证书列表
    • keys:PEM 格式的 SSL 证书私钥列表

    APISIX 会将相同下标的证书和私钥配对使用,因此 certskeys 列表的长度必须一致。

    APISIX 目前支持在多处设置 CA 证书,比如 等。

    在这些地方,使用 ssl_trusted_certificatetrusted_ca_cert 来配置 CA 证书,但是这些配置最终将转化为 OpenResty 的 lua_ssl_trusted_certificate 指令。

    如果你需要在不同的地方指定不同的 CA 证书,你可以将这些 CA 证书制作成一个 CA bundle 文件,在需要用到 CA 证书的地方将配置指向这个文件。这样可以避免生成的 lua_ssl_trusted_certificate 存在多处并且互相覆盖的问题。

    下面用一个完整的例子来展示如何在 APISIX 设置多个 CA 证书。

    下表详细列出这个示例所涉及到的配置及其作用:

    1. 制作 CA bundle 文件
    1. cat /path/to/foo_ca.crt /path/to/bar_ca.crt > apisix.ca-bundle
    1. 启动 ETCD 集群,并开启客户端验证

    先编写 goreman 配置,命名为 Procfile-single-enable-mtls,内容如下:

    1. # 运行 `go get github.com/mattn/goreman` 安装 goreman,用 goreman 执行以下命令:
    2. 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
    3. 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 集群:

    1. 更新 config.yaml

    conf/config.yaml

    1. deployment:
    2. admin:
    3. admin_key
    4. - name: admin
    5. key: edd1c9f034335f136f87ad84b625c8f1
    6. role: admin
    7. admin_listen:
    8. ip: 127.0.0.1
    9. port: 9180
    10. admin_api_mtls:
    11. admin_ssl_ca_cert: /path/to/apisix.ca-bundle
    12. admin_ssl_cert: /path/to/foo_server.crt
    13. admin_ssl_cert_key: /path/to/foo_server.key
    14. apisix:
    15. ssl:
    16. ssl_trusted_certificate: /path/to/apisix.ca-bundle
    17. deployment:
    18. role: traditional
    19. role_traditional:
    20. config_provider: etcd
    21. etcd:
    22. host:
    23. - "https://127.0.0.1:12379"
    24. - "https://127.0.0.1:22379"
    25. - "https://127.0.0.1:32379"
    26. tls:
    27. cert: /path/to/bar_apisix.crt
    28. key: /path/to/bar_apisix.key
    29. sni: etcd.cluster.dev
    1. 测试 Admin API

    启动 APISIX,如果 APISIX 启动成功,logs/error.log 中没有异常输出,表示 APISIX 与 ETCD 之间进行 mTLS 通信正常。

    用 curl 模拟客户端,与 APISIX Admin API 进行 mTLS 通信,并创建一条路由:

    1. curl -vvv \
    2. --resolve 'admin.apisix.dev:9180:127.0.0.1' https://admin.apisix.dev:9180/apisix/admin/routes/1 \
    3. --cert /path/to/foo_client.crt \
    4. --key /path/to/foo_client.key \
    5. --cacert /path/to/apisix.ca-bundle \
    6. -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
    7. {
    8. "uri": "/get",
    9. "upstream": {
    10. "type": "roundrobin",
    11. "nodes": {
    12. "httpbin.org:80": 1
    13. }
    14. }
    15. }'
    1. 验证 APISIX 代理
    1. curl http://127.0.0.1:9080/get -i
    2. HTTP/1.1 200 OK
    3. Content-Type: application/json
    4. Content-Length: 298
    5. Connection: keep-alive
    6. Date: Tue, 26 Jul 2022 16:31:00 GMT
    7. Access-Control-Allow-Origin: *
    8. Access-Control-Allow-Credentials: true
    9. Server: APISIX/2.14.1
    10. ……

    APISIX 将请求代理到了上游 httpbin.org/get 路径,并返回了 HTTP/1.1 200 OK。整个过程使用 CA bundle 替代 CA 证书是正常可用的。