安全网关
Ingress 流量控制任务描述了如何配置入口网关以向外部公开 HTTP 服务。此任务描述如何使用 TLS 或 mTLS 公开安全的 HTTPS 服务。
准备工作
参考安装指南部署 Istio。
部署 httpbin 示例:
$ kubectl apply -f @samples/httpbin/httpbin.yaml@
对于 macOS 用户,请验证您是否使用通过 LibreSSL 库编译的
curl
:$ curl --version | grep LibreSSL curl 7.54.0 (x86_64-apple-darwin17.0) libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0
如果上述命令输出的是如图所示的 LibreSSL 版本,则
curl
命令应按照此任务中的说明正确运行。 否则,请尝试使用curl
的其他实现,例如在 Linux 机器上。
生成客户端和服务器证书和密钥
对于此任务,您可以使用自己喜欢的工具来生成证书和密钥。 下面的命令使用 openssl。
创建用于服务签名的根证书和私钥:
$ mkdir example_certs1 $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs1/example.com.key -out example_certs1/example.com.crt
为
httpbin.example.com
创建证书和私钥:$ openssl req -out example_certs1/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 0 -in example_certs1/httpbin.example.com.csr -out example_certs1/httpbin.example.com.crt
创建第二组相同类型的证书和密钥:
$ mkdir example_certs2 $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs2/example.com.key -out example_certs2/example.com.crt $ openssl req -out example_certs2/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs2/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs2/example.com.crt -CAkey example_certs2/example.com.key -set_serial 0 -in example_certs2/httpbin.example.com.csr -out example_certs2/httpbin.example.com.crt
为
helloworld.example.com
生成证书和私钥:$ openssl req -out example_certs1/helloworld.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/helloworld.example.com.key -subj "/CN=helloworld.example.com/O=helloworld organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/helloworld.example.com.csr -out example_certs1/helloworld.example.com.crt
生成客户端证书和私钥:
$ openssl req -out example_certs1/client.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/client.example.com.key -subj "/CN=client.example.com/O=client organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/client.example.com.csr -out example_certs1/client.example.com.crt
配置单机 TLS 入口网关
为入口网关创建 Secret:
$ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs1/httpbin.example.com.key \ --cert=example_certs1/httpbin.example.com.crt
配置入口网关:
首先,使用 servers:
为 443 端口定义一个网关,并将 credentialName
的值设置为 httpbin-credential
。
该值与 Secret 的名称相同。TLS 模式的值应为 SIMPLE
。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # 使用 istio 默认入口网关
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential # 必须与 Secret 相同
hosts:
- httpbin.example.com
EOF
接下来,通过定义相应的虚拟服务来配置网关的入口流量路由:
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com"
gateways:
- mygateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
EOF
最后,按照这些说明
设置访问网关的 INGRESS_HOST
和 SECURE_INGRESS_PORT
变量。
首先,创建一个 Kubernetes Gateway:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOF
接下来,通过定义相应的 HTTPRoute
配置网关的入口流量路由:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["httpbin.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /status
- path:
type: PathPrefix
value: /delay
backendRefs:
- name: httpbin
port: 8000
EOF
最后,从 Gateway
资源中获取网关地址和端口:
$ kubectl wait --for=condition=programmed gtw mygateway -n istio-system
$ export INGRESS_HOST=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.status.addresses[0].value}')
$ export SECURE_INGRESS_PORT=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="https")].port}')
向
httpbin
服务发送 HTTPS 请求:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... I'm a teapot! ...
httpbin
服务将返回 418 I’m a Teapot 代码。通过删除网关的 Secret 然后使用不同的证书和密钥重新创建它来更改网关的凭据:
$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs2/httpbin.example.com.key \ --cert=example_certs2/httpbin.example.com.crt
使用新的证书链和
curl
来访问httpbin
服务:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs2/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... I'm a teapot! ...
如果您使用之前的证书链来访问
httpbin
,则会失败:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (OUT), TLS alert, Server hello (2): * curl: (35) error:04FFF06A:rsa routines:CRYPTO_internal:block type is not 01
为多个主机配置 TLS 入口网关
您可以为多个主机(例如 httpbin.example.com
和 helloworld.example.com
)配置入口网关。
入口网关配置有与每个主机相对应的唯一凭据。
通过删除并使用原始证书和密钥重新创建 Secret 来恢复上一个示例中的
httpbin
凭据:$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs1/httpbin.example.com.key \ --cert=example_certs1/httpbin.example.com.crt
启动
helloworld-v1
示例:$ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l service=helloworld $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l version=v1
创建
helloworld-credential
Secret:$ kubectl create -n istio-system secret tls helloworld-credential \ --key=example_certs1/helloworld.example.com.key \ --cert=example_certs1/helloworld.example.com.crt
使用
httpbin.example.com
和helloworld.example.com
主机配置入口网关:
为 443 端口定义一个具有两个服务器部分的网关。将每个端口上的 credentialName
值分别设置为 httpbin-credential
和 helloworld-credential
。将 TLS 模式设置为 SIMPLE
。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # 使用 istio 默认入口网关
servers:
- port:
number: 443
name: https-httpbin
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential
hosts:
- httpbin.example.com
- port:
number: 443
name: https-helloworld
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: helloworld-credential
hosts:
- helloworld.example.com
EOF
通过定义相应的虚拟服务来配置网关的流量路由。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- helloworld.example.com
gateways:
- mygateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld
port:
number: 5000
EOF
在 443 端口上配置具有两个监听器的 Gateway
。将每个端口的监听器的 certificateRefs
的名字分别设置为 httpbin-credential
和 helloworld-credential
。
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https-httpbin
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
- name: https-helloworld
hostname: "helloworld.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: helloworld-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOF
为 helloworld
服务配置网关的流量路由:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: helloworld
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["helloworld.example.com"]
rules:
- matches:
- path:
type: Exact
value: /hello
backendRefs:
- name: helloworld
port: 5000
EOF
向
helloworld.example.com
发送 HTTPS 请求:$ curl -v -HHost:helloworld.example.com --resolve "helloworld.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://helloworld.example.com:$SECURE_INGRESS_PORT/hello" ... HTTP/2 200 ...
向
httpbin.example.com
发送一个 HTTPS 请求,仍然返回一个 HTTP 418:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... server: istio-envoy ...
配置双向 TLS 入口网关
您可以扩展网关的定义以支持双向 TLS。
通过删除其 Secret 并创建一个新的来更改入口网关的凭据。服务器使用 CA 证书来验证其客户端,我们必须使用名称
ca.crt
来持有 CA 证书。$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret generic httpbin-credential \ --from-file=tls.key=example_certs1/httpbin.example.com.key \ --from-file=tls.crt=example_certs1/httpbin.example.com.crt \ --from-file=ca.crt=example_certs1/example.com.crt
配置入口网关:
更改网关的定义以将 TLS 模式设置为 MUTUAL
。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # 使用 istio 默认入口网关
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: MUTUAL
credentialName: httpbin-credential # 必须与 Secret 相同
hosts:
- httpbin.example.com
EOF
因为 Kubernetes Gateway API 目前不支持
Gateway
中的双向 TLS 终止,所以我们使用 Istio 特定的选项 gateway.istio.io/tls-terminate-mode: MUTUAL
来配置它:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
options:
gateway.istio.io/tls-terminate-mode: MUTUAL
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOF
尝试使用之前的方法发送 HTTPS 请求,看看它是如何失败的:
$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" * 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): * TLSv1.3 (IN), TLS alert, unknown (628): * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
将客户端证书和私钥传递给
curl
并重新发送请求。将带有--cert
标志的客户证书和带有--key
标志的私钥传递给curl
:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt --cert example_certs1/client.example.com.crt --key example_certs1/client.example.com.key \ "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... server: istio-envoy ... I'm a teapot! ...
更多信息
密钥格式
Istio 支持读取几种不同的 Secret 格式,以支持与各种工具的集成,例如 cert-manager:
- 带有
tls.key
和tls.crt
的 TLS Secret,如上所述。对于双向 TLS,ca.crt
可以作为密钥。 - 如上所述,TLS Secret 具有密钥
tls.key
和tls.crt
。 对于双向 TLS,单独的通用 Secret 名为<secret>-cacert
,带有cacert
密钥。 例如,httpbin-credential
具有tls.key
和tls.crt
,而httpbin-credential-cacert
具有cacert
。 - 带有
key
和cert
键的通用 Secret。对于双向 TLS,cacert
可以作为密钥。 - 带有
key
和cert
键的通用 Secret。对于双向 TLS,名为<secret>-cacert
的带有cacert
键的通用 Secret。 例如,httpbin-credential
有key
和cert
,httpbin-credential-cacert
有cacert
。 - 对于双向 TLS,可以使用
caCertCredentialName
引用带有cacert
或ca.crt
键的单独通用 Secret。它优先于使用credentialName(s)
引用的 Secret 中的 CA 证书。 cacert
键值可以是一个 CA 捆绑包,由串联的各个 CA 证书组成。
SNI 路由
HTTPS Gateway
将在转发请求之前对其配置的主机执行 SNI
匹配,这可能会导致某些请求失败。有关详细信息,
请参阅配置 SNI 路由。
问题排查
检查
INGRESS_HOST
和SECURE_INGRESS_PORT
环境变量的值。根据以下命令的输出,确保它们具有有效值:$ kubectl get svc -n istio-system $ echo "INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT"
确保
INGRESS_HOST
的值是一个 IP 地址。在某些云平台(例如 AWS)中,您可能会得到一个域名而不是 IP 地址。 此任务需要一个 IP 地 址,因此您需要使用类似以下的命令进行转换:$ nslookup ab52747ba608744d8afd530ffd975cbf-330887905.us-east-1.elb.amazonaws.com $ export INGRESS_HOST=3.225.207.109
检查网关控制器的日志以获取错误消息:
$ kubectl logs -n istio-system <gateway-service-pod>
验证已在
istio-system
命名空间中成功创建 Secret:$ kubectl -n istio-system get secrets
httpbin-credential
和helloworld-credential
应当显示在 Secret 列表中。检查日志以验证入口网关代理已将密钥/证书对推送到入口网关:
$ kubectl logs -n istio-system <gateway-service-pod>
日志应显示
httpbin-credential
Secret 已添加。如果使用双向 TLS, 那么httpbin-credential-cacert
Secret 也应该出现。 验证日志显示网关代理接收到来自入口网关的 SDS 请求,资源的名称是httpbin-credential
, 并且入口网关获得了密钥/证书对。如果使用双向 TLS,日志应显示密钥/证书已发送到入口网关, 网关代理收到带有httpbin-credential-cacert
资源名称的 SDS 请求,并且入口网关获得了根证书。
清理
- 删除网关配置和路由:
$ kubectl delete gateway mygateway
$ kubectl delete virtualservice httpbin helloworld
$ kubectl delete -n istio-system gtw mygateway
$ kubectl delete httproute httpbin helloworld
删除 Secret、证书和密钥:
$ kubectl delete -n istio-system secret httpbin-credential helloworld-credential $ rm -rf ./example_certs1 ./example_certs2
关闭
httpbin
和helloworld
服务:$ kubectl delete -f samples/httpbin/httpbin.yaml $ kubectl delete deployment helloworld-v1 $ kubectl delete service helloworld