安全网关

控制入口流量任务” 描述了如何配置入口网关以将 HTTP 服务公开给外部流量。此任务展示了如何使用简单 TLS 或双向 TLS 公开安全的 HTTPS 服务。

开始之前

  • 按照 安装指南 中的说明设置 Istio。

  • 启动 httpbin 示例。

    压缩
    $ kubectl apply -f @samples/httpbin/httpbin.yaml@
    
  • 对于 macOS 用户,请验证您是否使用的是使用 LibreSSL 库编译的 curl

    $ 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 机器上。

生成客户端和服务器证书和密钥

此任务需要多组证书和密钥,这些证书和密钥在以下示例中使用。您可以使用您喜欢的工具创建它们,也可以使用以下命令使用 openssl 生成它们。

  1. 创建根证书和私钥以签署服务的证书。

    $ mkdir example_certs1
    $ 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 生成证书和私钥。

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

    $ mkdir example_certs2
    $ 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
    $ 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"
    $ 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 生成证书和私钥。

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

    $ 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"
    $ 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
    

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

  1. 为入口网关创建密钥。

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

首先,定义一个网关,其中包含用于端口 443 的 servers: 部分,并为 credentialName 指定值为 httpbin-credential。这些值与密钥的名称相同。TLS 模式应具有 SIMPLE 值。

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: httpbin-credential # must be the same as secret
    hosts:
    - httpbin.example.com
EOF

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

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

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

  1. 发送 HTTPS 请求以通过 HTTPS 访问 httpbin 服务。

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    I'm a teapot!
    ...
    

    httpbin 服务将返回 418 我是一个茶壶 代码。

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

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

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs2/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    I'm a teapot!
    ...
    
  4. 如果您尝试使用以前的证书链访问 httpbin,则尝试现在将失败。

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

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

您可以为多个主机(例如,httpbin.example.comhelloworld.example.com)配置入口网关。入口网关配置有对应于每个主机的唯一凭据。

  1. 通过删除并使用原始证书和密钥重新创建密钥来恢复先前示例中的 httpbin 凭据。

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

    压缩压缩
    $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l service=helloworld
    $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l version=v1
    
  3. 创建一个 helloworld-credential 密钥。

    $ kubectl create -n istio-system secret tls helloworld-credential \
      --key=example_certs1/helloworld.example.com.key \
      --cert=example_certs1/helloworld.example.com.crt
    
  4. 配置具有主机 httpbin.example.comhelloworld.example.com 的入口网关。

定义一个网关,其中包含两个用于端口 443 的服务器部分。分别将每个端口上的 credentialName 值设置为 httpbin-credentialhelloworld-credential。将 TLS 模式设置为 SIMPLE

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https-httpbin
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: httpbin-credential
    hosts:
    - httpbin.example.com
  - port:
      number: 443
      name: https-helloworld
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: helloworld-credential
    hosts:
    - helloworld.example.com
EOF

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

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
  - helloworld.example.com
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        host: helloworld
        port:
          number: 5000
EOF
  1. 发送 HTTPS 请求到 helloworld.example.com

    $ curl -v -HHost:helloworld.example.com --resolve "helloworld.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://helloworld.example.com:$SECURE_INGRESS_PORT/hello"
    ...
    HTTP/2 200
    ...
    
  2. 发送 HTTPS 请求到 httpbin.example.com 并仍然获得 HTTP 418 作为返回。

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    server: istio-envoy
    ...
    

配置双向 TLS 入口网关

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

  1. 通过删除其密钥并创建一个新的密钥来更改入口网关的凭据。服务器使用 CA 证书验证其客户端,并且我们必须使用密钥 ca.crt 来保存 CA 证书。

    $ kubectl -n istio-system delete secret httpbin-credential
    $ kubectl create -n istio-system secret generic httpbin-credential \
      --from-file=tls.key=example_certs1/httpbin.example.com.key \
      --from-file=tls.crt=example_certs1/httpbin.example.com.crt \
      --from-file=ca.crt=example_certs1/example.com.crt
    
  2. 配置入口网关。

更改网关的定义以将 TLS 模式设置为 MUTUAL

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: MUTUAL
      credentialName: httpbin-credential # must be the same as secret
    hosts:
    - httpbin.example.com
EOF
  1. 尝试使用之前的方法发送 HTTPS 请求,并查看它如何失败。

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):
    * TLSv1.3 (IN), TLS handshake, Server hello (2):
    * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
    * TLSv1.3 (IN), TLS handshake, Request CERT (13):
    * TLSv1.3 (IN), TLS handshake, Certificate (11):
    * TLSv1.3 (IN), TLS handshake, CERT verify (15):
    * TLSv1.3 (IN), TLS handshake, Finished (20):
    * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
    * TLSv1.3 (OUT), TLS handshake, Certificate (11):
    * TLSv1.3 (OUT), TLS handshake, Finished (20):
    * TLSv1.3 (IN), TLS alert, unknown (628):
    * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
    
  2. 将客户端证书和私钥传递给 curl 并重新发送请求。使用 --cert 标志将客户端的证书传递给 curl,并使用 --key 标志传递私钥。

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
      --cacert example_certs1/example.com.crt --cert example_certs1/client.example.com.crt --key example_certs1/client.example.com.key \
      "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
    HTTP/2 418
    ...
    server: istio-envoy
    ...
    I'm a teapot!
    ...
    

更多信息

密钥格式

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

  • 具有密钥 tls.keytls.crt 的 TLS 密钥,如上所述。对于双向 TLS,可以使用 ca.crt 密钥。
  • 具有密钥 keycert 的通用密钥。对于双向 TLS,可以使用 cacert 密钥。
  • 具有密钥 keycert 的通用密钥。对于双向 TLS,一个单独的通用密钥名为 <secret>-cacert,其中包含 cacert 密钥。例如,httpbin-credential 包含 keycert,而 httpbin-credential-cacert 包含 cacert
  • cacert 密钥值可以是包含连接的各个 CA 证书的 CA 捆绑包。

SNI 路由

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

故障排除

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

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

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

    $ kubectl logs -n istio-system <gateway-service-pod>
    
  • 如果使用 macOS,请验证您是否正在使用使用 LibreSSL 库编译的 curl,如 开始之前 部分所述。

  • 验证密钥是否已成功在 istio-system 命名空间中创建。

    $ kubectl -n istio-system get secrets
    

    httpbin-credentialhelloworld-credential 应显示在密钥列表中。

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

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

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

清理

  1. 删除网关配置和路由。
$ kubectl delete gateway mygateway
$ kubectl delete virtualservice httpbin helloworld
  1. 删除密钥、证书和密钥。

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

    $ kubectl delete -f samples/httpbin/httpbin.yaml
    $ kubectl delete deployment helloworld-v1
    $ kubectl delete service helloworld
    
这些信息是否有用?
您对改进有什么建议?

感谢您的反馈!