使用 SDS 为 Gateway 提供 HTTPS 加密支持

    双向 TLS 所需的私钥、服务器证书以及根证书都由 Secret 发现服务(SDS)完成配置。

    1. 首先执行 Ingress 任务的初始化步骤,然后执行 部分中获取 Ingress 的地址和端口,在完成这些步骤之后,也就是完成了 Istio 和 的部署,并设置了 和 SECURE_INGRESS_PORT 两个环境变量的值。

    2. macOS 用户应该检查一下本机的 curl 是否是使用 LibreSSL 库进行编译的:

      如果上面的命令输出了一段 LibreSSL 的版本信息,就说明你的 curl 命令可以完成本任务的内容。否则就要想办法换一个不同的 curl 了,例如可以换用一台运行 Linux 的工作站。

    如果使用配置了 ingress gateway ,并且想要迁移 ingress gateway 使用 SDS 方法。无需其他步骤。

    为服务器和客户端生成证书

    可以使用各种常用工具来生成证书和私钥。这个例子中用了一个来自 的脚本来完成工作。

    1. 克隆:

      1. $ git clone https://github.com/nicholasjackson/mtls-go-example
    2. 进入代码库文件夹:

      1. $ pushd mtls-go-example
    3. httpbin.example.com 生成证书。注意要把下面命令中的 password 替换为其它值。

      1. $ ./generate.sh httpbin.example.com <password>

      看到提示后,所有问题都输入 Y 即可。这个命令会生成四个目录:1_root2_intermediate3_application 以及 4_client。这些目录中包含了后续过程所需的客户端和服务端证书。

    4. 把证书移动到 httpbin.example.com 目录之中:

      1. $ mkdir ../httpbin.example.com && mv 1_root 2_intermediate 3_application 4_client ../httpbin.example.com
    5. 返回之前的目录:

      1. $ popd

    可以配置 TLS Ingress Gateway ,让它从 Ingress Gateway 代理通过 SDS 获取凭据。Ingress Gateway 代理和 Ingress Gateway 在同一个 Pod 中运行,监视 Ingress Gateway 所在命名空间中新建的 Secret。在 Ingress Gateway 中启用 SDS 具有如下好处:

    • Ingress Gateway 无需重启,就可以动态的新增、删除或者更新密钥/证书对以及根证书。

    • 无需加载 Secret 卷。创建了 kubernetes Secret 之后,这个 Secret 就会被 Gateway 代理捕获,并以密钥/证书对和根证书的形式发送给 Ingress Gateway 。

    • Gateway 代理能够监视多个密钥/证书对。只需要为每个主机名创建 Secret 并更新 Gateway 定义就可以了。

    1. 在 Ingress Gateway 上启用 SDS,并部署 Ingress Gateway 代理。 这个功能缺省是禁用的,因此需要在 Helm 中打开 istio-ingressgateway.sds.enabled 开关,然后生成 istio-ingressgateway.yaml 文件:

      1. $ istioctl manifest generate \
      2. --set values.gateways.istio-egressgateway.enabled=false \
      3. --set values.gateways.istio-ingressgateway.sds.enabled=true > \
      4. $HOME/istio-ingressgateway.yaml
      5. $ kubectl apply -f $HOME/istio-ingressgateway.yaml
    2. 设置两个环境变量:INGRESS_HOSTSECURE_INGRESS_PORT

      1. $ export SECURE_INGRESS_PORT=$(kubectl -n istio-system \
      2. get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].port}')
      3. $ export INGRESS_HOST=$(kubectl -n istio-system \
      4. get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    1. 启动 httpbin 样例:

      1. $ cat <<EOF | kubectl apply -f -
      2. apiVersion: v1
      3. kind: Service
      4. metadata:
      5. name: httpbin
      6. labels:
      7. app: httpbin
      8. spec:
      9. ports:
      10. - name: http
      11. port: 8000
      12. selector:
      13. app: httpbin
      14. ---
      15. apiVersion: apps/v1
      16. kind: Deployment
      17. metadata:
      18. name: httpbin
      19. spec:
      20. replicas: 1
      21. selector:
      22. matchLabels:
      23. app: httpbin
      24. version: v1
      25. template:
      26. metadata:
      27. labels:
      28. app: httpbin
      29. version: v1
      30. spec:
      31. containers:
      32. - image: docker.io/citizenstig/httpbin
      33. imagePullPolicy: IfNotPresent
      34. name: httpbin
      35. ports:
      36. - containerPort: 8000
      37. EOF
    2. 为 Ingress Gateway 创建 Secret:

      1. $ kubectl create -n istio-system secret generic httpbin-credential \
      2. --from-file=key=httpbin.example.com/3_application/private/httpbin.example.com.key.pem \
      3. --from-file=cert=httpbin.example.com/3_application/certs/httpbin.example.com.cert.pem

      secret name 不能istio 或者 prometheus为开头, 且 secret 不能 包含 token 字段。

      1. $ cat <<EOF | kubectl apply -f -
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: mygateway
      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. credentialName: "httpbin-credential" # must be the same as secret
      17. hosts:
      18. - "httpbin.example.com"
      19. EOF
    3. 配置 Gateway 的 Ingress 流量路由,并配置对应的 VirtualService

      1. $ cat <<EOF | kubectl apply -f -
      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. - mygateway
      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. 用 HTTPS 协议访问 httpbin 服务:

      httpbin 服务会返回 。

    5. 使用 curl 访问 httpbin 服务:

      1. $ curl -v -HHost:httpbin.example.com \
      2. --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \
      3. --cacert httpbin.new.example.com/2_intermediate/certs/ca-chain.cert.pem \
      4. https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      5. ...
      6. HTTP/2 418
      7. ...
      8. -=[ teapot ]=-
      9. _...._
      10. .' _ _ `.
      11. | ."` ^ `". _,
      12. \_;`"---"`|//
      13. | ;/
      14. \_ _/
      15. `"""`
    6. 如果尝试使用之前的证书链来再次访问 httpbin,就会得到失败的结果:

      1. $ curl -v -HHost:httpbin.example.com \
      2. --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \
      3. --cacert httpbin.example.com/2_intermediate/certs/ca-chain.cert.pem \
      4. https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      5. ...
      6. * TLSv1.2 (OUT), TLS handshake, Client hello (1):
      7. * TLSv1.2 (IN), TLS handshake, Server hello (2):
      8. * TLSv1.2 (IN), TLS handshake, Certificate (11):
      9. * TLSv1.2 (OUT), TLS alert, Server hello (2):
      10. * SSL certificate problem: unable to get local issuer certificate

    可以把多个主机名配置到同一个 Ingress Gateway 上,例如 httpbin.example.comhelloworld-v1.example.com。Ingress Gateway 会为每个 credentialName 获取一个唯一的凭据。

    1. 要恢复 “httpbin” 的凭据,请删除对应的 secret 并重新创建。

      1. $ kubectl -n istio-system delete secret httpbin-credential
      2. $ kubectl create -n istio-system secret generic httpbin-credential \
      3. --from-file=key=httpbin.example.com/3_application/private/httpbin.example.com.key.pem \
      4. --from-file=cert=httpbin.example.com/3_application/certs/httpbin.example.com.cert.pem
    2. 启动 hellowworld-v1 示例:

      1. $ cat <<EOF | kubectl apply -f -
      2. apiVersion: v1
      3. kind: Service
      4. metadata:
      5. name: helloworld-v1
      6. labels:
      7. app: helloworld-v1
      8. spec:
      9. ports:
      10. - name: http
      11. port: 5000
      12. selector:
      13. app: helloworld-v1
      14. ---
      15. apiVersion: apps/v1
      16. kind: Deployment
      17. metadata:
      18. name: helloworld-v1
      19. spec:
      20. replicas: 1
      21. selector:
      22. matchLabels:
      23. app: helloworld-v1
      24. version: v1
      25. template:
      26. metadata:
      27. labels:
      28. app: helloworld-v1
      29. version: v1
      30. spec:
      31. containers:
      32. - name: helloworld
      33. image: istio/examples-helloworld-v1
      34. resources:
      35. requests:
      36. cpu: "100m"
      37. imagePullPolicy: IfNotPresent #Always
      38. ports:
      39. - containerPort: 5000
      40. EOF
    3. 为 Ingress Gateway 创建一个 Secret。如果已经创建了 httpbin-credential,就可以创建 helloworld-credential Secret 了。

      1. $ pushd mtls-go-example
      2. $ ./generate.sh helloworld-v1.example.com <password>
      3. $ mkdir ../helloworld-v1.example.com && mv 1_root 2_intermediate 3_application 4_client ../helloworld-v1.example.com
      4. $ popd
      5. $ kubectl create -n istio-system secret generic helloworld-credential \
      6. --from-file=key=helloworld-v1.example.com/3_application/private/helloworld-v1.example.com.key.pem \
      7. --from-file=cert=helloworld-v1.example.com/3_application/certs/helloworld-v1.example.com.cert.pem
    4. 定义一个 Gateway ,其中包含了两个 server,都开放了 443 端口。两个 credentialName 字段分别赋值为 httpbin-credentialhelloworld-credential。设置 TLS 的 mode 为 SIMPLE

      1. $ cat <<EOF | kubectl apply -f -
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: mygateway
      6. spec:
      7. selector:
      8. istio: ingressgateway # use istio default ingress gateway
      9. servers:
      10. - port:
      11. number: 443
      12. name: https-httpbin
      13. protocol: HTTPS
      14. tls:
      15. mode: SIMPLE
      16. credentialName: "httpbin-credential"
      17. hosts:
      18. - "httpbin.example.com"
      19. - port:
      20. number: 443
      21. name: https-helloworld
      22. protocol: HTTPS
      23. tls:
      24. mode: SIMPLE
      25. credentialName: "helloworld-credential"
      26. hosts:
      27. - "helloworld-v1.example.com"
      28. EOF
    5. 配置 Gateway 的流量路由,配置 VirtualService

      1. $ cat <<EOF | kubectl apply -f -
      2. metadata:
      3. name: helloworld-v1
      4. spec:
      5. hosts:
      6. - "helloworld-v1.example.com"
      7. gateways:
      8. - mygateway
      9. http:
      10. - match:
      11. - uri:
      12. exact: /hello
      13. route:
      14. - destination:
      15. host: helloworld-v1
      16. port:
      17. number: 5000
      18. EOF
    6. helloworld-v1.example.com 发送 HTTPS 请求:

      1. $ curl -v -HHost:helloworld-v1.example.com \
      2. --resolve helloworld-v1.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \
      3. --cacert helloworld-v1.example.com/2_intermediate/certs/ca-chain.cert.pem \
      4. https://helloworld-v1.example.com:$SECURE_INGRESS_PORT/hello
      5. HTTP/2 200
    7. 发送 HTTPS 请求到 httpbin.example.com,还是会看到茶壶:

      1. $ curl -v -HHost:httpbin.example.com \
      2. --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \
      3. --cacert httpbin.example.com/2_intermediate/certs/ca-chain.cert.pem \
      4. https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      5. -=[ teapot ]=-
      6. _...._
      7. .' _ _ `.
      8. | ."` ^ `". _,
      9. \_;`"---"`|//
      10. | ;/
      11. \_ _/
      12. `"""`

    可以对 Gateway 的定义进行扩展,加入双向 TLS 的支持。要修改 Ingress Gateway 的凭据,就要删除并重建对应的 Secret。服务器会使用 CA 证书对客户端进行校验,因此需要使用 cacert 字段来保存 CA 证书:

    1. 修改 Gateway 定义,设置 TLS 的模式为 MUTUAL

      1. $ cat <<EOF | kubectl apply -f -
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: mygateway
      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. credentialName: "httpbin-credential" # must be the same as secret
      17. hosts:
      18. - "httpbin.example.com"
      19. EOF
    2. 使用前面的方式尝试发出 HTTPS 请求,会看到失败的过程:

      1. $ curl -v -HHost:httpbin.example.com \
      2. --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \
      3. --cacert httpbin.example.com/2_intermediate/certs/ca-chain.cert.pem \
      4. https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      5. * TLSv1.3 (OUT), TLS handshake, Client hello (1):
      6. * TLSv1.3 (IN), TLS handshake, Server hello (2):
      7. * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
      8. * TLSv1.3 (IN), TLS handshake, Request CERT (13):
      9. * TLSv1.3 (IN), TLS handshake, Certificate (11):
      10. * TLSv1.3 (IN), TLS handshake, CERT verify (15):
      11. * TLSv1.3 (IN), TLS handshake, Finished (20):
      12. * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
      13. * TLSv1.3 (OUT), TLS handshake, Certificate (11):
      14. * TLSv1.3 (OUT), TLS handshake, Finished (20):
      15. * TLSv1.3 (IN), TLS alert, unknown (628):
      16. * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
    3. curl 命令中加入客户端证书和私钥的参数,重新发送请求。(客户端证书参数为 --cert,私钥参数为 --key

      1. $ curl -v -HHost:httpbin.example.com \
      2. --resolve httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \
      3. --cacert httpbin.example.com/2_intermediate/certs/ca-chain.cert.pem \
      4. --cert httpbin.example.com/4_client/certs/httpbin.example.com.cert.pem \
      5. --key httpbin.example.com/4_client/private/httpbin.example.com.key.pem \
      6. https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418
      7. -=[ teapot ]=-
      8. _...._
      9. .' _ _ `.
      10. | ."` ^ `". _,
      11. \_;`"---"`|//
      12. | ;/
      13. \_ _/
    4. 如果不想用 httpbin-credential secret 来存储所有的凭据, 可以创建两个单独的 secret :

      • httpbin-credential 用来存储服务器的秘钥和证书
      • httpbin-credential-cacert 用来存储客户端的 CA 证书且一定要有 -cacert 后缀

      使用以下命令创建两个单独的 secret :

      1. $ kubectl -n istio-system delete secret httpbin-credential
      2. $ kubectl create -n istio-system secret generic httpbin-credential \
      3. --from-file=key=httpbin.example.com/3_application/private/httpbin.example.com.key.pem \
      4. --from-file=cert=httpbin.example.com/3_application/certs/httpbin.example.com.cert.pem
      5. $ kubectl create -n istio-system secret generic httpbin-credential-cacert \
      6. --from-file=cacert=httpbin.example.com/2_intermediate/certs/ca-chain.cert.pem

    故障排查

    • 查看 INGRESS_HOSTSECURE_INGRESS_PORT 环境变量。根据下面的输出内容,确认其中是否包含了有效的值:

      1. $ kubectl get svc -n istio-system
      2. $ echo INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT
      1. $ kubectl logs -n istio-system $(kubectl get pod -l istio=ingressgateway \
      2. -n istio-system -o jsonpath='{.items[0].metadata.name}') -c istio-proxy
    • 如果使用的是 macOS,检查其编译信息,确认其中包含 LibreSSL,具体步骤在一节中有具体描述。

    • 校验在 istio-system 命名空间中是否成功创建了 Secret

      1. $ kubectl -n istio-system get secrets

      httpbin-credentialhelloworld-credential 都应该出现在列表之中。

    • 检查日志,看 Ingress Gateway 代理是否已经成功的把密钥和证书对推送给了 Ingress Gateway :

      1. $ kubectl logs -n istio-system $(kubectl get pod -l istio=ingressgateway \
      2. -n istio-system -o jsonpath='{.items[0].metadata.name}') -c ingress-sds

      正常情况下,日志中应该显示 httpbin-credential 已经成功创建。如果使用的是双向 TLS,还应该看到 httpbin-credential-cacert。通过对日志的查看,能够验证 Ingress Gateway 代理从 Ingress Gateway 收到了 SDS 请求,资源名称是 httpbin-credential,Ingress Gateway 最后得到了应有的密钥/证书对。如果使用的是双向 TLS,日志会显示出密钥/证书对已经发送给 Ingress Gateway ,Gateway 代理接收到了资源名为 httpbin-credential-cacert 的 SDS 请求,Ingress Gateway 用这种方式获取根证书。

    1. 删除 Gateway 配置、VirtualService 以及 Secret

      1. $ kubectl delete gateway mygateway
      2. $ kubectl delete virtualservice httpbin
      3. $ kubectl delete --ignore-not-found=true -n istio-system secret httpbin-credential \
      4. helloworld-credential
      5. $ kubectl delete --ignore-not-found=true virtualservice helloworld-v1
    2. 删除证书目录以及用于生成证书的代码库:

      1. $ rm -rf httpbin.example.com helloworld-v1.example.com mtls-go-example
    3. 删除用于重新部署 Ingress Gateway 的文件:

      1. 关闭 和 helloworld-v1 服务:

      相关内容

      把 Istio 入口网关配置为外部服务的代理。

      使用 Cert-Manager 部署一个自定义 Ingress 网关

      如何使用 cert-manager 手工部署一个自定义 Ingress 网关。

      描述如何在 AWS 上使用网络负载均衡器配置 Istio Ingress。

      Ingress Gateway

      描述如何配置 Istio gateway,以将服务暴露至服务网格之外。

      使用文件挂载的证书并通过 TLS 或 mTLS 将服务暴露至服务网格之外。

      无 TLS 终止的 Ingress Gateway

      说明了如何为一个 ingress gateway 配置 SNI 透传。