配置客户端与网关之间的 mTLS
双向 TLS(mTLS)是一种客户端和服务器彼此进行身份验证的双向 TLS 协议,通常用于防止未授权访问并增强安全性。
本文介绍如何通过 Ingress Controller 配置网关 ,要求客户端使用双向 TLS(mTLS)。
前提条件
在网关上启用 SSL
确保你的网关已启用 SSL。
- APISIX 网关
- API7 网关
helm upgrade apisix apisix/apisix \
--set ... \ # add other parameters
--set "apisix.ssl.enabled=true" \
--set "apisix.ssl.containerPort=9443"
默认情况下,API7 网关已在 container port 9443 和 service port 443 上启用 SSL,因此此步骤无需额外配置。
生成证书和密钥
生成证书颁发机构(CA)的密钥和证书:
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -sha256 \
-key ca.key \
-out ca.crt \
-subj "/CN=MyTestCA" \
-extensions v3_ca \
-config <(printf "[req]\ndistinguished_name=req\n[ v3_ca ]\nbasicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer")
生成服务端密钥和证书签名请求(CSR):
openssl genrsa -out server.key 2048
openssl req -new -sha256 \
-key server.key \
-out server.csr \
-subj "/CN=test.com"
使用 CA 证书对服务端 CSR 进行签名,生成服务端证书:
openssl x509 -req -days 36500 -sha256 \
-in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth")
为客户端生成密钥和证书签名请求(CSR):
openssl genrsa -out client.key 2048
openssl req -new -sha256 \
-key client.key \
-out client.csr \
-subj "/CN=CLIENT"
使用 CA 证书对客户端 CSR 进行签名,生成客户端证书:
openssl x509 -req -days 36500 -sha256 \
-in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth")
为网关配置 mTLS
- Gateway API
- APISIX CRD
Ingress Controller 目前不支持使用 Gateway API 配置客户端与网关之间的 mTLS。
创建两个 Kubernetes Secret:
kubectl create secret tls test-mtls-secret \
--cert=server.crt \
--key=server.key \
--namespace=aic
kubectl create secret generic test-ca-secret \
--from-file=cert=ca.crt \
--namespace=aic
创建 Kubernetes manifest 文件,定义 mTLS 配置:
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
namespace: aic
name: test-mtls
spec:
ingressClassName: apisix
hosts:
- test.com
secret:
name: test-mtls-secret
namespace: aic
client:
caSecret:
name: test-ca-secret
namespace: aic
depth: 1
将配置应用到集群:
kubectl apply -f mtls.yaml
创建路由
- Gateway API
- APISIX CRD
Ingress Controller 目前不支持使用 Gateway API 配置客户端与网关之间的 mTLS。
创建一个示例路由,将路径为 /ip 的所有请求转发到上游服务 httpbin.org:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: httpbin-ip
spec:
ingressClassName: apisix
http:
- name: httpbin-ip
match:
paths:
- /ip
upstreams:
- name: httpbin-external-domain
将配置应用到集群:
kubectl apply -f httpbin-route.yaml
验证
如需验证客户端与网关之间的 mTLS,先将网关的 SSL 端口转发到本地:
kubectl port-forward svc/<your-gateway-svc-name> 9443:443 &
使用客户端证书
由于证书仅对 CN test.com 有效,应使用 test.com 作为网关域名。携带客户端证书向路由发送请求:
curl -ikv --resolve "test.com:9443:127.0.0.1" "https://test.com:9443/ip" \
--cert client.crt --key client.key
类似如下的 mTLS 握手过程表示客户端与网关之间的 mTLS 已启用:
* Added test.com:9443:127.0.0.1 to DNS cache
* Hostname test.com was found in DNS cache
* Trying 127.0.0.1:9443...
* Connected to test.com (127.0.0.1) port 9443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* 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_SHA384
...
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
HTTP/2 200
...
请注意,网关和客户端已在握手期间成功验证彼此的证书并建立连接。
不使用客户端证书
向 https://test.com:9443/ip 发送请求,但不携带客户端证书:
curl -ikv --resolve "test.com:9443:127.0.0.1" "https://test.com:9443/ip"
失败的 mTLS 握手类似如下:
* Added test.com:9443:127.0.0.1 to DNS cache
* Hostname test.com was found in DNS cache
* Trying 127.0.0.1:9443...
* Connected to test.com (127.0.0.1) port 9443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* 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, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
...
* TLSv1.3 (IN), TLS alert, unknown (628):
* OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
* Failed receiving HTTP2 data
* OpenSSL SSL_write: SSL_ERROR_ZERO_RETURN, errno 0
* Failed sending HTTP2 data
* Connection #0 to host test.com left intact
由于缺少客户端证书,握手失败。