安全网关

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

Istio 包括了对 Kubernetes Gateway API 的 Beta 支持, 打算未来使其成为流量管理的默认 API。 以下说明指导您在网格中配置流量管理时如何选择使用 Gateway API 或 Istio 配置 API。 请按照您的首选项遵循 Gateway APIIstio APIs 页签中的指示说明。

请注意,Kubernetes Gateway API CRD 不会默认安装在大多数 Kubernetes 集群上, 因此请确保在使用 Gateway API 之前已安装好这些 CRD:

  1. $ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
  2. { kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.8.0" | kubectl apply -f -; }

准备工作

  • 参考安装指南部署 Istio。

  • 部署 httpbin 示例:

    Zip

    1. $ kubectl apply -f @samples/httpbin/httpbin.yaml@
  • 对于 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. $ mkdir example_certs1
    2. $ 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
  2. httpbin.example.com 创建证书和私钥:

    1. $ 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"
    2. $ 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
  3. 创建第二组相同类型的证书和密钥:

    1. $ mkdir example_certs2
    2. $ 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
    3. $ 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"
    4. $ 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
  4. helloworld.example.com 生成证书和私钥:

    1. $ 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"
    2. $ 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
  5. 生成客户端证书和私钥:

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

您可以通过运行以下命令确认您拥有所有需要的文件:

  1. $ ls example_cert*
  2. example_certs1:
  3. client.example.com.crt example.com.key httpbin.example.com.crt
  4. client.example.com.csr helloworld.example.com.crt httpbin.example.com.csr
  5. client.example.com.key helloworld.example.com.csr httpbin.example.com.key
  6. example.com.crt helloworld.example.com.key
  7. example_certs2:
  8. example.com.crt httpbin.example.com.crt httpbin.example.com.key
  9. example.com.key httpbin.example.com.csr

配置单机 TLS 入口网关

  1. 为入口网关创建 Secret:

    1. $ kubectl create -n istio-system secret tls httpbin-credential \
    2. --key=example_certs1/httpbin.example.com.key \
    3. --cert=example_certs1/httpbin.example.com.crt
  2. 配置入口网关:

首先,使用 servers: 为 443 端口定义一个网关,并将 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

接下来,通过定义相应的虚拟服务来配置网关的入口流量路由:

  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

最后,按照这些说明 设置访问网关的 INGRESS_HOSTSECURE_INGRESS_PORT 变量。

首先,创建一个 Kubernetes Gateway

  1. $ cat <<EOF | kubectl apply -f -
  2. apiVersion: gateway.networking.k8s.io/v1beta1
  3. kind: Gateway
  4. metadata:
  5. name: mygateway
  6. namespace: istio-system
  7. spec:
  8. gatewayClassName: istio
  9. listeners:
  10. - name: https
  11. hostname: "httpbin.example.com"
  12. port: 443
  13. protocol: HTTPS
  14. tls:
  15. mode: Terminate
  16. certificateRefs:
  17. - name: httpbin-credential
  18. allowedRoutes:
  19. namespaces:
  20. from: Selector
  21. selector:
  22. matchLabels:
  23. kubernetes.io/metadata.name: default
  24. EOF

接下来,通过定义相应的 HTTPRoute 配置网关的入口流量路由:

  1. $ cat <<EOF | kubectl apply -f -
  2. apiVersion: gateway.networking.k8s.io/v1beta1
  3. kind: HTTPRoute
  4. metadata:
  5. name: httpbin
  6. spec:
  7. parentRefs:
  8. - name: mygateway
  9. namespace: istio-system
  10. hostnames: ["httpbin.example.com"]
  11. rules:
  12. - matches:
  13. - path:
  14. type: PathPrefix
  15. value: /status
  16. - path:
  17. type: PathPrefix
  18. value: /delay
  19. backendRefs:
  20. - name: httpbin
  21. port: 8000
  22. EOF

最后,从 Gateway 资源中获取网关地址和端口:

  1. $ kubectl wait --for=condition=programmed gtw mygateway -n istio-system
  2. $ export INGRESS_HOST=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.status.addresses[0].value}')
  3. $ export SECURE_INGRESS_PORT=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="https")].port}')
  1. httpbin 服务发送 HTTPS 请求:

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example_certs1/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. `"""`

    httpbin 服务将返回 418 I’m a Teapot 代码。

  2. 通过删除网关的 Secret 然后使用不同的证书和密钥重新创建它来更改网关的凭据:

    1. $ kubectl -n istio-system delete secret httpbin-credential
    2. $ kubectl create -n istio-system secret tls httpbin-credential \
    3. --key=example_certs2/httpbin.example.com.key \
    4. --cert=example_certs2/httpbin.example.com.crt
  3. 使用新的证书链和 curl 来访问 httpbin 服务:

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example_certs2/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. `"""`
  4. 如果您使用之前的证书链来访问 httpbin,则会失败:

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example_certs1/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. 通过删除并使用原始证书和密钥重新创建 Secret 来恢复上一个示例中的 httpbin 凭据:

    1. $ kubectl -n istio-system delete secret httpbin-credential
    2. $ kubectl create -n istio-system secret tls httpbin-credential \
    3. --key=example_certs1/httpbin.example.com.key \
    4. --cert=example_certs1/httpbin.example.com.crt
  2. 启动 helloworld-v1 示例:

    ZipZip

    1. $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l service=helloworld
    2. $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l version=v1
  3. 创建 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
  4. 使用 httpbin.example.comhelloworld.example.com 主机配置入口网关:

为 443 端口定义一个具有两个服务器部分的网关。将每个端口上的 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.example.com
  28. EOF

通过定义相应的虚拟服务来配置网关的流量路由。

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

在 443 端口上配置具有两个监听器的 Gateway。将每个端口的监听器的 certificateRefs 的名字分别设置为 httpbin-credentialhelloworld-credential

  1. $ cat <<EOF | kubectl apply -f -
  2. apiVersion: gateway.networking.k8s.io/v1beta1
  3. kind: Gateway
  4. metadata:
  5. name: mygateway
  6. namespace: istio-system
  7. spec:
  8. gatewayClassName: istio
  9. listeners:
  10. - name: https-httpbin
  11. hostname: "httpbin.example.com"
  12. port: 443
  13. protocol: HTTPS
  14. tls:
  15. mode: Terminate
  16. certificateRefs:
  17. - name: httpbin-credential
  18. allowedRoutes:
  19. namespaces:
  20. from: Selector
  21. selector:
  22. matchLabels:
  23. kubernetes.io/metadata.name: default
  24. - name: https-helloworld
  25. hostname: "helloworld.example.com"
  26. port: 443
  27. protocol: HTTPS
  28. tls:
  29. mode: Terminate
  30. certificateRefs:
  31. - name: helloworld-credential
  32. allowedRoutes:
  33. namespaces:
  34. from: Selector
  35. selector:
  36. matchLabels:
  37. kubernetes.io/metadata.name: default
  38. EOF

helloworld 服务配置网关的流量路由:

  1. $ cat <<EOF | kubectl apply -f -
  2. apiVersion: gateway.networking.k8s.io/v1beta1
  3. kind: HTTPRoute
  4. metadata:
  5. name: helloworld
  6. spec:
  7. parentRefs:
  8. - name: mygateway
  9. namespace: istio-system
  10. hostnames: ["helloworld.example.com"]
  11. rules:
  12. - matches:
  13. - path:
  14. type: Exact
  15. value: /hello
  16. backendRefs:
  17. - name: helloworld
  18. port: 5000
  19. EOF
  1. helloworld.example.com 发送 HTTPS 请求:

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

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

配置双向 TLS 入口网关

您可以扩展网关的定义以支持双向 TLS

  1. 通过删除其 Secret 并创建一个新的来更改入口网关的凭据。服务器使用 CA 证书来验证其客户端,我们必须使用名称 cacert 来持有 CA 证书。

    1. $ kubectl -n istio-system delete secret httpbin-credential
    2. $ kubectl create -n istio-system secret generic httpbin-credential \
    3. --from-file=tls.key=example_certs1/httpbin.example.com.key \
    4. --from-file=tls.crt=example_certs1/httpbin.example.com.crt \
    5. --from-file=ca.crt=example_certs1/example.com.crt

    如有必要,凭据可以包含一个证书吊销列表 (CRL), 使用 ca.crl 作为键名。如果是这样,请在上述示例中添加另一个参数来提供 CRL:--from-file=ca.crl=/some/path/to/your-crl.pem

    凭据也可以包括 OCSP Staple 使用参数 --from-file=tls.ocsp-staple=/some/path/to/your-ocsp-staple.pem 指定的 tls.ocsp-staple 作为键名。

  2. 配置入口网关:

更改网关的定义以将 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

因为 Kubernetes Gateway API 目前不支持 Gateway 中的双向 TLS 终止,所以我们使用 Istio 特定的选项 gateway.istio.io/tls-terminate-mode: MUTUAL 来配置它:

  1. $ cat <<EOF | kubectl apply -f -
  2. apiVersion: gateway.networking.k8s.io/v1beta1
  3. kind: Gateway
  4. metadata:
  5. name: mygateway
  6. namespace: istio-system
  7. spec:
  8. gatewayClassName: istio
  9. listeners:
  10. - name: https
  11. hostname: "httpbin.example.com"
  12. port: 443
  13. protocol: HTTPS
  14. tls:
  15. mode: Terminate
  16. certificateRefs:
  17. - name: httpbin-credential
  18. options:
  19. gateway.istio.io/tls-terminate-mode: MUTUAL
  20. allowedRoutes:
  21. namespaces:
  22. from: Selector
  23. selector:
  24. matchLabels:
  25. kubernetes.io/metadata.name: default
  26. EOF
  1. 尝试使用之前的方法发送 HTTPS 请求,看看它是如何失败的:

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example_certs1/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
  2. 将客户端证书和私钥传递给 curl 并重新发送请求。将带有 --cert 标志的客户证书和带有 --key 标志的私钥传递给 curl

    1. $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    2. --cacert example_certs1/example.com.crt --cert example_certs1/client.example.com.crt --key example_certs1/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 的带有 cacert 键的通用 Secret。 例如,httpbin-credentialkeycerthttpbin-credential-cacertcacert
  • cacert 键值可以是一个 CA 捆绑包,由串联的各个 CA 证书组成。

SNI 路由

HTTPS Gateway 将在转发请求之前对其配置的主机执行 SNI 匹配,这可能会导致某些请求失败。有关详细信息, 请参阅配置 SNI 路由

问题排查

  • 检查 INGRESS_HOSTSECURE_INGRESS_PORT 环境变量的值。根据以下命令的输出,确保它们具有有效值:

    1. $ kubectl get svc -n istio-system
    2. $ echo "INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT"
  • 确保 INGRESS_HOST 的值是一个 IP 地址。在某些云平台(例如 AWS)中,您可能会得到一个域名而不是 IP 地址。 此任务需要一个 IP 地 址,因此您需要使用类似以下的命令进行转换:

    1. $ nslookup ab52747ba608744d8afd530ffd975cbf-330887905.us-east-1.elb.amazonaws.com
    2. $ export INGRESS_HOST=3.225.207.109
  • 检查网关控制器的日志以获取错误消息:

    1. $ kubectl logs -n istio-system <gateway-service-pod>
  • 如果使用 macOS,请验证您使用的是使用 LibreSSL curl 库编译的,如准备工作部分中所述。

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

    1. $ kubectl -n istio-system get secrets

    httpbin-credentialhelloworld-credential 应当显示在 Secret 列表中。

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

    1. $ kubectl logs -n istio-system <gateway-service-pod>

    日志应显示 httpbin-credential Secret 已添加。如果使用双向 TLS, 那么 httpbin-credential-cacert Secret 也应该出现。 验证日志显示网关代理接收到来自入口网关的 SDS 请求,资源的名称是 httpbin-credential, 并且入口网关获得了密钥/证书对。如果使用双向 TLS,日志应显示密钥/证书已发送到入口网关, 网关代理收到带有 httpbin-credential-cacert 资源名称的 SDS 请求,并且入口网关获得了根证书。

清理

  1. 删除网关配置和路由:
  1. $ kubectl delete gateway mygateway
  2. $ kubectl delete virtualservice httpbin helloworld
  1. $ kubectl delete -n istio-system gtw mygateway
  2. $ kubectl delete httproute httpbin helloworld
  1. 删除 Secret、证书和密钥:

    1. $ kubectl delete -n istio-system secret httpbin-credential helloworld-credential
    2. $ rm -rf ./example_certs1 ./example_certs2
  2. 关闭 httpbinhelloworld 服务:

    1. $ kubectl delete -f samples/httpbin/httpbin.yaml
    2. $ kubectl delete deployment helloworld-v1
    3. $ kubectl delete service helloworld