用 HTTPS 加密 Gateway
本文任务使用了新的 v1alpha3 流量控制 API。旧版本 API 已经过时,会在下一个 Istio 版本中移除。如果需要使用旧版本 API,请阅读旧版本文档
控制 Ingress 流量任务描述了如何对 Ingress gateway 进行配置,从而对外以 HTTP 端点的形式暴露服务。本文中将会对这一任务进行扩展,为服务启用普通或双向 TLS 保护,以 HTTPS 的形式对网格外提供服务。
开始之前
执行开始之前的步骤,并且确定 Ingress 地址和端口。在完成这些步骤后,应该已经部署了可用的 Istio 服务网格以及 httpbin 应用了,并且
INGRESS_HOST
和SECURE_INGRESS_PORT
这两个变量也已经生成并赋值。macOS 用户需要注意,要检查一下
curl
的编译是否包含了 LibreSSL 库:$ 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 系统。
生成客户端与服务器的证书和密钥
这里可以使用任意工具来生成证书和密钥,下面我们使用了一个脚本:
克隆 https://github.com/nicholasjackson/mtls-go-example 仓库:
$ git clone https://github.com/nicholasjackson/mtls-go-example
进入代码目录:
$ cd mtls-go-example
生成证书(任意指定密码)
$ ./generate.sh httpbin.example.com <password>
所有提示问题都选择
y
。这一命令会生成四个目录:1_root
、2_intermediate
、3_application
以及4_client
,其中包含了后续步骤所需的客户端和服务器的证书。
配置 TLS ingress gateway
接下来就要为 Ingress gateway 开放一个 443 端口,用于提供 HTTPS 服务。首先使用密钥和证书作为输入,创建一个 Secret 。然后定义 Gateway 对象,其中包含了一个使用 443
端口的 server
。
创建一个 Kubernetes
sceret
对象,用于保存服务器的证书和私钥。具体说来就是使用kubectl
命令在命名空间istio-system
中创建一个 secret 对象,命名为istio-ingressgateway-certs
。Istio 网关会自动载入这个 secret。这里的 secret 必须 在
istio-system
命名空间中,并且命名为istio-ingressgateway-certs
,否则就不会被正确载入,也就无法在 Istio gateway 中使用了。$ kubectl create -n istio-system secret tls istio-ingressgateway-certs --key 3_application/private/httpbin.example.com.key.pem --cert 3_application/certs/httpbin.example.com.cert.pem secret "istio-ingressgateway-certs" created
注意缺省情况下,
istio-system
命名空间中所有的 Service account 都是可以访问 Secret 的,所以可能会泄漏私钥。可以通过 RBAC 设置来进行对涉密数据的保护。定义一个 Gateway 对象,其中包含了使用 443 端口的
server
部分。证书的私钥的位置 必须 是
/etc/istio/ingressgateway-certs
,否则 Gateway 无法载入。cat <<EOF | istioctl create -f - apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: httpbin-gateway spec: selector: istio: ingressgateway # 使用 Istio 的缺省 Gateway servers: - port: number: 443 name: https protocol: HTTPS tls: mode: SIMPLE serverCertificate: /etc/istio/ingressgateway-certs/tls.crt privateKey: /etc/istio/ingressgateway-certs/tls.key hosts: - "httpbin.example.com" EOF
为通过 Gateway 进入的流量进行路由配置。配置一个和控制 Ingress 流量任务 中一致的
Virtualservice
:cat <<EOF | istioctl create -f - apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin spec: hosts: - "httpbin.example.com" gateways: - httpbin-gateway http: - match: - uri: prefix: /status - uri: prefix: /delay route: - destination: port: number: 8000 host: httpbin EOF
用
curl
发送https
请求到SECURE_INGRESS_PORT
,也就是通过 HTTPS 协议访问httpbin
服务。--resolve
选项要求curl
通过域名httpbin.example.com
使用 TLS 访问 Gateway 地址,这样也就符合了证书的 SNI 要求。--cacert
参数则让curl
命令使用刚刚生成的证书来对服务器进行校验。发送请求到
/status/418
,会看到漂亮的返回内容,这说明我们成功访问了httpbin
。httpbin
服务会返回 418 I'm a Teapot。$ curl -v --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert 2_intermediate/certs/ca-chain.cert.pem https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418 ... Server certificate: subject: C=US; ST=Denial; L=Springfield; O=Dis; CN=httpbin.example.com start date: Jun 24 18:45:18 2018 GMT expire date: Jul 4 18:45:18 2019 GMT common name: httpbin.example.com (matched) issuer: C=US; ST=Denial; O=Dis; CN=httpbin.example.com SSL certificate verify ok. ... HTTP/2 418 ... -=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
Gateway 定义的传播可能需要一些时间,在传播完成之间的访问,可能会得到这样的错误响应:
Failed to connect to httpbin.example.com port <your secure port>: Connection refused
。只需等待一分钟,重新访问即可。查看
curl
命令返回内容中的Server certificate
部分,注意其中的common name
:common name: httpbin.example.com (matched)
。另外输出中还包含了SSL certificate verify ok
,这说明对服务器的证书校验是成功的,返回状态码为 418 和一只茶杯犬。
如果需要支持 双向 TLS,请继续下一节内容。
配置 Ingress gateway 的双向 TLS 支持
这一节中会再次对 Gateway 定义进行扩展,从而在从外部客户端到 Gateway 的访问中添加对 双向 TLS 的支持。
创建一个 Kubernetes secret,用于存储 CA 证书,服务器会使用这一证书来对客户端进行校验。用
kubectl
在istio-system
命名空间中创建 Secretistio-ingressgateway-ca-certs
。Istio gateway 会自动载入这个 Secret。这个 secret 必须 以
istio-ingressgateway-ca-certs
为名并保存在命名空间istio-system
之中,否则 Istio gateway 无法正确完成加载过程。$ kubectl create -n istio-system secret generic istio-ingressgateway-ca-certs --from-file=2_intermediate/certs/ca-chain.cert.pem secret "istio-ingressgateway-ca-certs" created
重新定义之前的 Gateway,把其中的
tls
一节的mode
字段的值修改为MUTUAL
,并给caCertificates
赋值:证书的位置 必须 是
/etc/istio/ingressgateway-ca-certs
,否则 Gateway 无法加载。证书的文件名必须和创建 Secret 时使用的文件名一致,这里就是ca-chain.cert.pem
cat <<EOF | istioctl replace -f - apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: httpbin-gateway spec: selector: istio: ingressgateway # 是用缺省的 Istio Gateway servers: - port: number: 443 name: https protocol: HTTPS tls: mode: MUTUAL serverCertificate: /etc/istio/ingressgateway-certs/tls.crt privateKey: /etc/istio/ingressgateway-certs/tls.key caCertificates: /etc/istio/ingressgateway-ca-certs/ca-chain.cert.pem hosts: - "httpbin.example.com" EOF
同样的使用 HTTPS 方式访问
httpbin
服务:$ curl --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert 2_intermediate/certs/ca-chain.cert.pem https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418 curl: (35) error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
Gateway 定义的传播可能需要一些时间,在传播完成之前,可能还会得到
418
的响应。稍事等待后,可再次执行curl
。因为服务拒绝接受未经验证的请求,这次访问会得到一个错误返回。因此这次调用必须使用客户端证书,并且需要把密钥传递给
curl
,从而完成对请求的签名过程。再次使用
curl
命令发送请求,这次的参数加入了客户端证书(--cert
)以及私钥(--key
):$ curl --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert 2_intermediate/certs/ca-chain.cert.pem --cert 4_client/certs/httpbin.example.com.cert.pem --key 4_client/private/httpbin.example.com.key.pem https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418 -=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
这次服务器成功校验了客户端证书并放行,因此就再次看到了正确的返回内容。
常见问题
查看
INGRESS_HOST
以及SECURE_INGRESS_PORT
这两个环境变量,确定它们的正确取值,具体命令:$ kubectl get svc -n istio-system $ echo INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT
检查
istio-ingressgateway
Pod 是否正确的加载了证书和私钥:$ kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/istio/ingressgateway-certs
tls.crt
和tls.key
都应该保存在这个目录中。检查 Ingress gateway 证书中的
Subject
字段的正确性:$ kubectl exec -i -n istio-system $(kubectl get pod -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') -- cat /etc/istio/ingressgateway-certs/tls.crt | openssl x509 -text -noout | grep 'Subject:' Subject: C=US, ST=Denial, L=Springfield, O=Dis, CN=httpbin.example.com
检查 Ingress gateway 的代理能够正确访问证书:
$ kubectl exec -ti $(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath={.items[0]..metadata.name}) -n istio-system -- curl 127.0.0.1:15000/certs { "ca_cert": "", "cert_chain": "Certificate Path: /etc/istio/ingressgateway-certs/tls.crt, Serial Number: 100212, Days until Expiration: 370" }
检查
istio-ingressgateway
中的错误信息:$ kubectl logs -n istio-system -l istio=ingressgateway
双向 TLS 常见问题
除去刚才提到的内容之外,执行下列检查:
检查
istio-ingressgateway
Pod 是否正确加载 CA 证书:$ kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/istio/ingressgateway-ca-certs
ca-chain.cert.pem
应该保存在这个路径中。检查 Ingress gateway 中 CA 证书的
Subject
字段内容:$ kubectl exec -i -n istio-system $(kubectl get pod -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') -- cat /etc/istio/ingressgateway-ca-certs/ca-chain.cert.pem | openssl x509 -text -noout | grep 'Subject:' Subject: C=US, ST=Denial, L=Springfield, O=Dis, CN=httpbin.example.com
清理
删除
Gateway
配置、VirtualService
以及 Secret 对象:$ istioctl delete gateway httpbin-gateway $ istioctl delete virtualservice httpbin $ kubectl delete --ignore-not-found=true -n istio-system secret istio-ingressgateway-certs istio-ingressgateway-ca-certs
关闭 httpbin 服务:
$ kubectl delete --ignore-not-found=true -f @samples/httpbin/httpbin.yaml@
See also
介绍在服务网格 Istio 中如何配置外部公开服务。
Istio v1alpha3 路由 API 介绍,动机及其设计原则。
描述如何在AWS上使用网络负载均衡器配置 Istio Ingress。
介绍更安全,低风险的部署和发布到生产。
描述基于 Istio 的 Bookinfo 示例的简单场景。
描述基于 Istio Bookinfo 示例的简单场景。