安全网关(文件挂载)

    TLS 所必需的私钥、服务器证书和根证书使用基于文件挂载的方式进行配置。

    1. 执行开始之前任务和任务中的确认 ingress 的 IP 和端口小节中的步骤。执行完毕后,Istio 和 服务都已经部署完毕。环境变量 INGRESS_HOSTSECURE_INGRESS_PORT 也已经设置。

    2. 对于 macOS 用户,确认您的 curl 使用了 LibreSSL 库来编译:

      如果以上输出打印了 LibreSSL 的版本,则 curl 应该可以按照此任务中的说明正常工作。否则,请尝试另一种 curl 版本,例如运行于 Linux 计算机的版本。

    生成服务器证书和私钥

    此任务您可以使用您喜欢的工具来生成证书和私钥。下列命令使用了 openssl

    1. 创建一个根证书和私钥以为您的服务所用的证书签名:

      1. $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
    2. httpbin.example.com 创建一个证书和私钥:

      1. $ openssl req -out httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
      2. $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in httpbin.example.com.csr -out httpbin.example.com.crt

    本节中,您将配置一个使用 443 端口的 ingress 网关,以处理 HTTPS 流量。 首先使用证书和私钥创建一个 secret。该 secret 将被挂载为 /etc/istio/ingressgateway-certs 路径下的一个文件。 然后您可以创建一个网关定义,它将配置一个运行于端口 443 的服务。

    1. 创建一个 Kubernetes secret 以保存服务器的证书和私钥。使用 kubectl 在命名空间 istio-system 下创建 secret istio-ingressgateway-certs。Istio 网关将会自动加载该 secret。

      该 secret 必须istio-system 命名空间下,且名为 istio-ingressgateway-certs,以与此任务中使用的 Istio 默认 ingress 网关的配置保持一致。

      1. $ kubectl create -n istio-system secret tls istio-ingressgateway-certs --key httpbin.example.com.key --cert httpbin.example.com.crt
      2. secret "istio-ingressgateway-certs" created

      请注意,默认情况下,istio-system 命名空间下的所有 pod 都能挂载这个 secret 并访问该私钥。您可以将 ingress 网关部署到一个单独的命名空间中,并在那创建 secret,这样就只有这个 ingress 网关 pod 才能挂载它。

      验证 tls.crttls.key 是否都已经挂载到 ingress 网关 pod 中:

      1. $ 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
    2. 为 443 端口定义 Gateway 并设置 server

      证书和私钥必须位于 /etc/istio/ingressgateway-certs,否则网关将无法加载它们。

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: httpbin-gateway
      6. spec:
      7. selector:
      8. istio: ingressgateway # use istio default ingress gateway
      9. servers:
      10. - port:
      11. number: 443
      12. name: https
      13. protocol: HTTPS
      14. tls:
      15. mode: SIMPLE
      16. serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      17. privateKey: /etc/istio/ingressgateway-certs/tls.key
      18. hosts:
      19. - "httpbin.example.com"
      20. EOF
    3. 配置路由以让流量从 Gateway 进入。定义与任务中相同的 VirtualService

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: VirtualService
      4. metadata:
      5. name: httpbin
      6. spec:
      7. hosts:
      8. - "httpbin.example.com"
      9. gateways:
      10. - httpbin-gateway
      11. http:
      12. - match:
      13. - uri:
      14. prefix: /status
      15. - uri:
      16. prefix: /delay
      17. route:
      18. - destination:
      19. port:
      20. number: 8000
      21. host: httpbin
      22. EOF
    4. 使用 curl 发送一个 https 请求到 SECURE_INGRESS_PORT 以通过 HTTPS 访问 httpbin 服务。

      --resolve 标志让 curl 在通过 TLS 访问网关 IP 时支持 SNIhttpbin.example.com--cacert 选项则让 curl 使用您创建的证书来验证服务器。

      -HHost:httpbin.example.com 标志也包含了,但只有当 SECURE_INGRESS_PORT 与实际网关端口(443)不同(例如,您通过映射的 NodePort 来访问服务)时才真正需要。

      通过发送请求到 /status/418 URL 路径,您可以很好地看到您的 httpbin 服务确实已被访问。 httpbin 服务将返回 代码。

      1. $ curl -v -HHost:httpbin.example.com --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert example.com.crt https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      2. ...
      3. Server certificate:
      4. subject: CN=httpbin.example.com; O=httpbin organization
      5. start date: Oct 27 19:32:48 2019 GMT
      6. expire date: Oct 26 19:32:48 2020 GMT
      7. common name: httpbin.example.com (matched)
      8. issuer: O=example Inc.; CN=example.com
      9. SSL certificate verify ok.
      10. SSL certificate verify ok.
      11. ...
      12. HTTP/2 418
      13. ...
      14. -=[ teapot ]=-
      15. _...._
      16. .' _ _ `.
      17. | ."` ^ `". _,
      18. \_;`"---"`|//
      19. | ;/
      20. `"""`

      网关定义传播需要时间,因此您可能会得到以下报错: Failed to connect to httpbin.example.com port <your secure port>: Connection refused。请稍后重新执行 curl 命令。

    配置双向 TLS ingress 网关

    本节中您将您的网关的定义从上一节中扩展为支持外部客户端和网关之间的。

    1. 创建一个 Kubernetes Secret 以保存服务端将用来验证它的客户端的 CA 证书。使用 kubectl 在命名空间 istio-system 中创建 secret istio-ingressgateway-ca-certs。Istio 网关将会自动加载该 secret。

      该 secret 必须istio-system 命名空间下,且名为 istio-ingressgateway-ca-certs,以与此任务中使用的 Istio 默认 ingress 网关的配置保持一致。

      1. $ kubectl create -n istio-system secret generic istio-ingressgateway-ca-certs --from-file=example.com.crt
      2. secret "istio-ingressgateway-ca-certs" created
    2. 重新定义之前的 Gateway,修改 TLS 模式为 MUTUAL,并指定 caCertificates

      证书必须位于 /etc/istio/ingressgateway-ca-certs,否则网关将无法加载它们。 证书的(短)文件名必须与您创建 secret 的名称相同,在本例中为 example.com.crt

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: httpbin-gateway
      6. spec:
      7. selector:
      8. istio: ingressgateway # use istio default ingress gateway
      9. servers:
      10. - port:
      11. number: 443
      12. name: https
      13. protocol: HTTPS
      14. tls:
      15. mode: MUTUAL
      16. serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      17. privateKey: /etc/istio/ingressgateway-certs/tls.key
      18. caCertificates: /etc/istio/ingressgateway-ca-certs/example.com.crt
      19. hosts:
      20. - "httpbin.example.com"
      21. EOF
    3. 像上一节中一样通过 HTTPS 访问 httpbin 服务:

      1. $ curl -HHost:httpbin.example.com --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert example.com.crt https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      2. curl: (35) error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure

      网关定义传播需要时间,因此您可能会仍然得到 418 状态码。请稍后重新执行 curl 命令。

      这次您将得到一个报错,因为服务端拒绝接受未认证的请求。您需要传递 curl 客户端证书和私钥以将请求签名。

    4. httpbin.example.com 服务创建客户端证书。您可以使用 httpbin-client.example.com URI 来指定客户端,或使用其它 URI。

      1. $ openssl req -out httpbin-client.example.com.csr -newkey rsa:2048 -nodes -keyout httpbin-client.example.com.key -subj "/CN=httpbin-client.example.com/O=httpbin's client organization"
      2. $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in httpbin-client.example.com.csr -out httpbin-client.example.com.crt
    5. 重新用 curl 发送之前的请求,这次通过参数传递客户端证书(添加 --cert 选项)和您的私钥(--key 选项):

      这次服务器成功执行了客户端身份验证,您再次收到了漂亮的茶壶图。

    本节中您将为多个主机(httpbin.example.combookinfo.com)配置 ingress 网关。 Ingress 网关将向客户端提供与每个请求的服务器相对应的唯一证书。

    与之前的小节不同,Istio 默认 ingress 网关无法立即使用,因为它仅被预配置为支持一个安全主机。 您需要先使用另一个 secret 配置并重新部署 ingress 网关服务器,然后才能使用它来处理第二台主机。

    1. $ openssl req -out bookinfo.com.csr -newkey rsa:2048 -nodes -keyout bookinfo.com.key -subj "/CN=bookinfo.com/O=bookinfo organization"
    2. $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in bookinfo.com.csr -out bookinfo.com.crt
    1. 创建一个新的 secret 以保存 bookinfo.com 的证书:

      1. $ kubectl create -n istio-system secret tls istio-ingressgateway-bookinfo-certs --key bookinfo.com.key --cert bookinfo.com.crt
      2. secret "istio-ingressgateway-bookinfo-certs" created
    2. 更新 istio-ingressgateway deployment 以挂载新创建的 secret。创建如下 gateway-patch.json 文件以更新 istio-ingressgateway deployment:

      1. $ cat > gateway-patch.json <<EOF
      2. [{
      3. "op": "add",
      4. "path": "/spec/template/spec/containers/0/volumeMounts/0",
      5. "value": {
      6. "mountPath": "/etc/istio/ingressgateway-bookinfo-certs",
      7. "name": "ingressgateway-bookinfo-certs",
      8. "readOnly": true
      9. }
      10. },
      11. {
      12. "op": "add",
      13. "path": "/spec/template/spec/volumes/0",
      14. "value": {
      15. "name": "ingressgateway-bookinfo-certs",
      16. "secret": {
      17. "secretName": "istio-ingressgateway-bookinfo-certs",
      18. "optional": true
      19. }
      20. }
      21. }]
      22. EOF
    3. 使用以下命令应用 istio-ingressgateway deployment 更新:

      1. $ kubectl -n istio-system patch --type=json deploy istio-ingressgateway -p "$(cat gateway-patch.json)"
    4. 验证 istio-ingressgateway pod 已成功加载私钥和证书:

      1. $ 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-bookinfo-certs

      tls.crttls.key 应该出现在文件夹之中。

    1. 部署 ,但不要部署网关:

      Zip

      1. $ kubectl apply -f @samples/bookinfo/platform/kube/bookinfo.yaml@
      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: bookinfo-gateway
      6. spec:
      7. selector:
      8. istio: ingressgateway # use istio default ingress gateway
      9. servers:
      10. number: 443
      11. name: https-bookinfo
      12. protocol: HTTPS
      13. tls:
      14. mode: SIMPLE
      15. serverCertificate: /etc/istio/ingressgateway-bookinfo-certs/tls.crt
      16. privateKey: /etc/istio/ingressgateway-bookinfo-certs/tls.key
      17. hosts:
      18. - "bookinfo.com"
    2. 配置 bookinfo.com 的路由。定义类似 的 VirtualService

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: VirtualService
      4. metadata:
      5. name: bookinfo
      6. spec:
      7. hosts:
      8. - "bookinfo.com"
      9. gateways:
      10. - bookinfo-gateway
      11. http:
      12. - match:
      13. - uri:
      14. exact: /productpage
      15. - uri:
      16. exact: /login
      17. - uri:
      18. exact: /logout
      19. - uri:
      20. prefix: /api/v1/products
      21. route:
      22. - destination:
      23. host: productpage
      24. port:
      25. number: 9080
      26. EOF
    3. 发送到 Bookinfo productpage 的请求:

      1. $ curl -o /dev/null -s -v -w "%{http_code}\n" -HHost:bookinfo.com --resolve bookinfo.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert example.com.crt -HHost:bookinfo.com https://bookinfo.com:$SECURE_INGRESS_PORT/productpage
      2. ...
      3. Server certificate:
      4. subject: CN=bookinfo.com; O=bookinfo organization
      5. start date: Oct 27 20:08:32 2019 GMT
      6. expire date: Oct 26 20:08:32 2020 GMT
      7. common name: bookinfo.com (matched)
      8. issuer: O=example Inc.; CN=example.com
      9. SSL certificate verify ok.
      10. ...
      11. 200
    4. 验证 httbin.example.com 像之前一样可访问。发送一个请求给它,您会再次看到您喜爱的茶壶:

      1. $ curl -HHost:httpbin.example.com --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert example.com.crt --cert httpbin-client.example.com.crt --key httpbin-client.example.com.key https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      2. ...
      3. -=[ teapot ]=-
      4. _...._
      5. .' _ _ `.
      6. | ."` ^ `". _,
      7. \_;`"---"`|//
      8. | ;/
      9. \_ _/
      10. `"""`

    问题排查

    • 检查环境变量 INGRESS_HOSTSECURE_INGRESS_PORT 的值。通过下列命令的输出确保它们都有有效值:

      1. $ kubectl get svc -n istio-system
      2. $ echo INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT
    • 验证 istio-ingressgateway pod 已经成功加载了私钥和证书:

      tls.crttls.key 应存在于文件夹之中。

    • 如果您已经创建了 istio-ingressgateway-certs secret,但是私钥和证书未加载,删掉 ingress 网关 pod 以强行重启 ingress 网关 pod 并重新加载私钥和证书。

      1. $ kubectl delete pod -n istio-system -l istio=ingressgateway
    • 验证 ingress 网关的证书的 Subject 是正确的:

      1. $ 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:'
      2. Subject: CN=httpbin.example.com, O=httpbin organization
    • 验证 ingress 网关的代理是否知道证书:

      1. $ kubectl exec -ti $(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') -n istio-system -- pilot-agent request GET certs
      2. {
      3. "ca_cert": "",
      4. "cert_chain": "Certificate Path: /etc/istio/ingressgateway-certs/tls.crt, Serial Number: 100212, Days until Expiration: 370"
      5. }
    • 检查 istio-ingressgateway 的日志看是否有错误消息:

      1. $ kubectl logs -n istio-system -l istio=ingressgateway
    • 对于 macOS 用户,验证您是否使用的是用 库编译的curl,如开始之前部分中所述。

    除了上一节中的步骤之外,请执行以下操作:

    • 验证 istio-ingressgateway pod 已经加载了 CA 证书:

      1. $ 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

      example.com.crt 应存在于文件夹之中。

    • 如果您已经创建了 istio-ingressgateway-ca-certs secret,但是 CA 证书未加载,删掉 ingress 网关 pod 以强行重新加载证书:

    • If you created the istio-ingressgateway-ca-certs secret, but the CA certificate is not loaded, delete the ingress gateway pod and force it to reload the certificate:

      1. $ kubectl delete pod -n istio-system -l istio=ingressgateway
    • 验证 ingress 网关的 CA 证书的 Subject 是正确的:

      1. $ 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/example.com.crt | openssl x509 -text -noout | grep 'Subject:'
      2. Subject: O=example Inc., CN=example.com
    1. 删除 Gateway 配置、VirtualService 和 secrets:

      1. $ kubectl delete gateway --ignore-not-found=true httpbin-gateway bookinfo-gateway
      2. $ kubectl delete virtualservice httpbin
      3. $ kubectl delete --ignore-not-found=true -n istio-system secret istio-ingressgateway-certs istio-ingressgateway-ca-certs
      4. $ kubectl delete --ignore-not-found=true virtualservice bookinfo
    2. 删除证书目录和用于生成证书的存储库:

      1. $ rm -rf example.com.crt example.com.key httpbin.example.com.crt httpbin.example.com.key httpbin.example.com.csr httpbin-client.example.com.crt httpbin-client.example.com.key httpbin-client.example.com.csr bookinfo.com.crt bookinfo.com.key bookinfo.com.csr
    3. 删除用于重新部署 istio-ingressgateway 的更新文件: