通过 SNI 代理 TLS 上的 TCP 流量
使用 Gateway API TLSRoute 或 APISIX CRD stream route,根据服务器名称指示(SNI)路由经过 TLS 加密的 TCP 流量。
前置条件
启动示例上游服务
创建 Kubernetes manifest 文件,定义 TCP echo 上游服务:
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: echo-tcp
spec:
replicas: 1
selector:
matchLabels:
app: echo-tcp
template:
metadata:
labels:
app: echo-tcp
spec:
containers:
- name: echo-tcp
image: alpine:3.20
command:
- sh
- -c
- |
apk add --no-cache socat &&
socat TCP-LISTEN:8443,reuseaddr,fork EXEC:"cat"
ports:
- containerPort: 8443
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: echo-tcp
spec:
selector:
app: echo-tcp
ports:
- name: tcp
port: 8443
targetPort: 8443
将配置应用到集群:
kubectl apply -f echo-tcp.yaml
启用网关 Stream 代理
升级网关,启用 stream 模式并设置 TCP 端口 9100。下列命令使用 --reuse-values 保留当前 Helm release 的其他值:
- APISIX 网关
- API7 网关
helm upgrade -n aic apisix apisix/apisix \
--reuse-values \
--set "service.stream.enabled=true" \
--set "service.stream.tcp[0].addr=9100" \
--set "service.stream.tcp[0].tls=true"
helm upgrade -n aic api7-ee-3-gateway api7/gateway \
--reuse-values \
--set "gateway.stream.enabled=true" \
--set "gateway.stream.only=false" \
--set "gateway.stream.tcp[0].addr=9100" \
--set "gateway.stream.tcp[0].tls=true"
生成证书和密钥
生成证书颁发机构(CA)的密钥和证书:
openssl genrsa -out ca.key 2048 && \
openssl req -new -sha256 -key ca.key -out ca.csr -subj "/CN=ROOTCA" && \
openssl x509 -req -days 36500 -sha256 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.crt
生成通用名称为 test.com 的密钥和证书,并使用 CA 证书进行签名:
openssl genrsa -out server.key 2048 && \
openssl req -new -sha256 -key server.key -out server.csr -subj "/CN=test.com" && \
openssl x509 -req -days 36500 -sha256 -extensions v3_req \
-CA ca.crt -CAkey ca.key -CAserial ca.srl -CAcreateserial \
-in server.csr -out server.crt
创建 Kubernetes TLS Secret:
kubectl create secret tls test-tls-secret \
--cert=server.crt \
--key=server.key \
--namespace=aic
配置网关 TLS
- Gateway API
- APISIX CRD
更新 Gateway manifest 文件,添加 TLS 监听器:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: aic
name: apisix
spec:
gatewayClassName: apisix
listeners:
- name: http
protocol: HTTP
port: 80
- name: tcptls
protocol: TLS
port: 9100
hostname: test.com
tls:
# TLS termination only. Passthrough is not supported.
certificateRefs:
- kind: Secret
group: ""
name: test-tls-secret
infrastructure:
parametersRef:
group: apisix.apache.org
kind: GatewayProxy
name: apisix-config
监听器的 port 不会在数据面打开端口。应用 Gateway 前,请先将网关 stream 代理配置为监听同一端口。该字段变化时,Ingress Controller 不会更新或重启数据面。
将配置应用到集群:
kubectl apply -f gateway.yaml
创建 Kubernetes manifest 文件,定义 TLS 配置:
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
namespace: aic
name: test-tls
spec:
ingressClassName: apisix
hosts:
- test.com
secret:
name: test-tls-secret
namespace: aic
将配置应用到集群:
kubectl apply -f tls.yaml
选择后端传输协议
本示例中的 TLS 监听器在网关终止客户端 TLS,而 echo-tcp 接收普通 TCP,因此应使用 tcp 上游 scheme。若后端自身接收 TLS,请将 scheme 设为 tls,使网关向上游重新建立 TLS 连接。
对于 Gateway API 路由,将 scheme 挂载到后端 Service 端口:
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: echo-tcp-transport
namespace: aic
spec:
targetRefs:
- group: ""
kind: Service
name: echo-tcp
sectionName: tcp
scheme: tcp
对于 APISIX CRD stream route,创建与后端 Service 同名的 ApisixUpstream:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
name: echo-tcp
namespace: aic
spec:
ingressClassName: apisix
scheme: tcp
应用与你所用路由类型对应的 manifest。不要在 HTTP 路由上使用 tcp、tls 或 udp;L4 scheme 会使 L7 上游不可达。
如需将 stream 插件挂载到 TLSRoute,请参阅将插件应用到 L4 路由。
创建路由
- Gateway API
- APISIX CRD
创建 Kubernetes manifest 文件,定义 TLSRoute:
apiVersion: gateway.networking.k8s.io/v1
kind: TLSRoute
metadata:
namespace: aic
name: tls-route
spec:
parentRefs:
- name: apisix
hostnames: ["test.com"]
rules:
- backendRefs:
- name: echo-tcp
port: 8443
Gateway 监听器和 TLSRoute 中都会出现 hostname 配置,因为它们各自承担不同用途:
- Gateway 监听器的
hostname:指定网关在该监听器上接受哪些主机名。 - TLSRoute 的
hostnames:定义路由适用的主机名,用于过滤监听器接收到的流量。
TLSRoute 中的 hostnames 是必填字段。路由至少有一个主机名必须与监听器主机名相交,路由才能挂载;与监听器不匹配的路由条目会被忽略。
创建 Kubernetes manifest 文件,定义 TCP 四层路 由(stream route):
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: tls-stream-route
namespace: aic
spec:
ingressClassName: apisix
stream:
- name: tls-stream-route
protocol: TCP
match:
ingressPort: 9100
backend:
serviceName: echo-tcp
servicePort: 8443
将配置应用到集群:
kubectl apply -f tls-tcp-route.yaml
验证
将网关的 TCP TLS Service 端口暴露到本地:
# 替换为你的网关 Service 名称
kubectl port-forward svc/<gateway-service-name> 9100:9100 &
运行以下命令,验证网关是否可以使用 SNI test.com 正确路由经过 TLS 加密的 TCP 流量:
openssl s_client -connect 127.0.0.1:9100 -servername test.com
TLS 握手完成后,可以输入任意文本并按 Enter。上游 echo server 会返回相同文本,说明网关已正确终止 TLS,并将明文 TCP 载荷转发到上游:
...
subject=CN=test.com
issuer=CN=ROOTCA
...
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 2048 bit
...
---
read R BLOCK
hello world
hello world
如需验证 SNI 未匹配任何已配置路由时的行为,运行:
openssl s_client -connect 127.0.0.1:9100 -servername random.com
由于网关无法识别匹配的四层路由(stream route),该连接会被终止。