Secure Gateways

Ingress 流量控制任务描述了如何配置入口网关以向外部公开 HTTP 服务。此任务描述如何使用 TLS 或 mTLS 公开安全的 HTTPS 服务。

准备工作

  1. 执行准备工作中的步骤。完成Ingress 流量控制确定 Ingress 的 IP 和端口部分任务。执行完这些步骤后,您应该已部署 Istio 和 httpbin服务,并设置了环境变量 INGRESS_HOSTSECURE_INGRESS_PORT

  2. 对于 macOS 用户,请验证您是否使用通过 LibreSSL 库编译的 curl:

    1. $ curl --version | grep LibreSSL
    2. 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

  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

配置单机TLS入口网关

  1. 确定已在准备工作环节完成httpbin服务的部署。

  2. 为 Ingress Gateway 创建 Secret:

    1. $ kubectl create -n istio-system secret tls httpbin-credential --key=httpbin.example.com.key --cert=httpbin.example.com.crt
  3. 为端口443定义一个带有 servers: 部分的网关,并将 credentialName 的值指定为 httpbin-credential。这些值与 Secret 名称相同。 TLS 模式的值应为 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
    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
  4. 配置网关的入口流量路由,定义相应的虚拟服务。

    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
  5. 发送 HTTPS 请求访问 httpbin 服务:

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"

    The httpbin service will return the 418 I’m a Teapot code.

  6. 删除网关的 secret,并创建一个新的 secret 来修改入口网关的凭据。

    1. $ kubectl -n istio-system delete secret httpbin-credential
    1. $ mkdir new_certificates
    2. $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout new_certificates/example.com.key -out new_certificates/example.com.crt
    3. $ openssl req -out new_certificates/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout new_certificates/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
    4. $ openssl x509 -req -sha256 -days 365 -CA new_certificates/example.com.crt -CAkey new_certificates/example.com.key -set_serial 0 -in new_certificates/httpbin.example.com.csr -out new_certificates/httpbin.example.com.crt
    5. $ kubectl create -n istio-system secret tls httpbin-credential \
    6. --key=new_certificates/httpbin.example.com.key \
    7. --cert=new_certificates/httpbin.example.com.crt
  7. curl 使用新证书链访问 httpbin 服务:

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert new_certificates/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    3. ...
    4. HTTP/2 418
    5. ...
    6. -=[ teapot ]=-
    7. _...._
    8. .' _ _ `.
    9. | ."` ^ `". _,
    10. \_;`"---"`|//
    11. | ;/
    12. \_ _/
    13. `"""`
  8. 如果使用先前的证书链访问httpbin,将返回失败。

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    3. ...
    4. * TLSv1.2 (OUT), TLS handshake, Client hello (1):
    5. * TLSv1.2 (IN), TLS handshake, Server hello (2):
    6. * TLSv1.2 (IN), TLS handshake, Certificate (11):
    7. * TLSv1.2 (OUT), TLS alert, Server hello (2):
    8. * curl: (35) error:04FFF06A:rsa routines:CRYPTO_internal:block type is not 01

为多个主机配置 TLS 入口网关

您可以为多个主机(例如 httpbin.example.comhelloworld-v1.example.com )配置入口网关。入口网关检索与特定凭据名称相对应的唯一凭据。

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

    1. $ kubectl -n istio-system delete secret httpbin-credential
    2. $ kubectl create -n istio-system secret tls httpbin-credential \
    3. --key=httpbin.example.com.key \
    4. --cert=httpbin.example.com.crt
  2. 启动 helloworld-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. helloworld-v1.example.com 生成证书和私钥:

    1. $ openssl req -out helloworld-v1.example.com.csr -newkey rsa:2048 -nodes -keyout helloworld-v1.example.com.key -subj "/CN=helloworld-v1.example.com/O=helloworld organization"
    2. $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in helloworld-v1.example.com.csr -out helloworld-v1.example.com.crt
  4. 创建 helloworld-credential secret:

    1. $ kubectl create -n istio-system secret tls helloworld-credential --key=helloworld-v1.example.com.key --cert=helloworld-v1.example.com.crt
  5. 为端口 443 定义一个包含两个 server 的网关。将每个端口上的 credentialName 的值分别设置为 httpbin-credentialhelloworld-credential 。将 TLS 模式设置为 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
  6. 配置网关的流量路由。定义相应的虚拟服务。

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

    1. $ curl -v -HHost:helloworld-v1.example.com --resolve "helloworld-v1.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example.com.crt "https://helloworld-v1.example.com:$SECURE_INGRESS_PORT/hello"
    3. HTTP/2 200
  8. 发送一个 HTTPS 请求到 httpbin.example.com,仍然返回一个茶壶:

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

配置双向 TLS 入口网关

您可以扩展网关的定义以支持双向TLS。删除入口网关的 secret 并创建一个新的,以更改入口网关的凭据。服务器使用 CA 证书来验证其客户端,并且必须使用名称 cacert 来持有 CA 证书。

  1. $ kubectl -n istio-system delete secret httpbin-credential
  2. $ kubectl create -n istio-system secret generic httpbin-credential --from-file=tls.key=httpbin.example.com.key \
  3. --from-file=tls.crt=httpbin.example.com.crt --from-file=ca.crt=example.com.crt
  1. 更改网关的定义, 将 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 --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    3. * TLSv1.3 (OUT), TLS handshake, Client hello (1):
    4. * TLSv1.3 (IN), TLS handshake, Server hello (2):
    5. * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
    6. * TLSv1.3 (IN), TLS handshake, Request CERT (13):
    7. * TLSv1.3 (IN), TLS handshake, Certificate (11):
    8. * TLSv1.3 (IN), TLS handshake, CERT verify (15):
    9. * TLSv1.3 (IN), TLS handshake, Finished (20):
    10. * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
    11. * TLSv1.3 (OUT), TLS handshake, Certificate (11):
    12. * TLSv1.3 (OUT), TLS handshake, Finished (20):
    13. * TLSv1.3 (IN), TLS alert, unknown (628):
    14. * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
  3. 生成客户端证书和私钥:

    1. $ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization"
    2. $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in client.example.com.csr -out client.example.com.crt
  4. 重新发送带客户端证书和私钥的 curl 请求。使用 –cert 标志传递客户端证书,使用 –key 标志传递私钥。

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

更多信息

密钥格式

Istio 支持读取不同的 Secret 格式,以支持与各种工具(例如cert-manager)的集成:

  • 如上所述,包含 tls.keytls.crt 的 TLS secret。对于双向 TLS,可以使用 ca.crt 密钥。
  • 包含 keycert 的通用 Secret。对于双向 TLS,可以使用 cacert 密钥。
  • 包含 keycert 的通用 Secret。对于双向 TLS,还可以单独设置名为 <secret>-cacert 的通用 secret,该 secret 含 cacert 密钥。例如,httpbin-credential 包含 keycert,而 httpbin-credential-cacert 包含 cacert
  • cacert 密钥可以是由单个 CA 证书连接组成的 CA 包。

SNI 路由

具有除 * 以外的 hosts 字段值的 HTTPS Gateway 将执行 SNI在转发请求之前匹配, 这可能会导致某些请求失败。有关详细信息,请参阅配置 SNI 路由

Troubleshooting

  • 检查 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 控制器的日志中是否有错误消息:

    1. $ kubectl logs -n istio-system "$(kubectl get pod -l istio=ingressgateway \
    2. -n istio-system -o jsonpath='{.items[0].metadata.name}')"
  • 如果使用 macOS,请按照准备工作部分中的说明,验证您正在使用通过LibreSSL库编译的 curl。

  • 验证 secret 是否已在 istio-system 命名空间中成功创建:

    1. $ kubectl -n istio-system get secrets

    httpbin-credentialhelloworld-credential 应该显示在 secret 列表中。

  • 检查日志以确认入口网关代理已将密钥/证书对推送到入口网关。

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

    日志应显示已添加httpbin-credential secret。如果使用双向 TLS,则还应显示 httpbin-credential-cacert secret。验证日志是否显示网关代理接收到来自入口网关的 SDS 请求(资源名称为 httpbin-credential),且入口网关已获得密钥/证书对。如果使用双向 TLS,则日志应显示密钥/证书已发送到入口网关,网关代理已收到带有 httpbin-credential-cacert资源名称的 SDS 请求,并且入口网关已获得根证书。

清除

  1. 删除网关配置,虚拟服务定义和 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 example.com.crt example.com.key httpbin.example.com.crt httpbin.example.com.key httpbin.example.com.csr helloworld-v1.example.com.crt helloworld-v1.example.com.key helloworld-v1.example.com.csr client.example.com.crt client.example.com.csr client.example.com.key ./new_certificates
  3. 关闭 httpbinhelloworld-v1 服务:

    1. $ kubectl delete deployment --ignore-not-found=true httpbin helloworld-v1
    2. $ kubectl delete service --ignore-not-found=true httpbin helloworld-v1