Istio 出口流量的 TLS


在网格内部直接通过 HTTP 协议访问外部加密服务

Istio 出口流量的 TLS

在网格内部直接通过 HTTP 协议访问外部加密服务

Fri Nov 16, 2018

2600 Words|Read in about 6 Min|本文总阅读量
Tags: istio   service mesh   kubernetes  

本文主要内容来自 Istio 官方文档,并对其进行了大量扩展和补充。

控制出口流量任务演示了如何从网格内部的应用程序访问 Kubernetes 集群外部的 HTTPHTTPS 服务, 如该主题中所述,默认情况下,启用了 Istio 的应用程序无法访问集群外的 URL, 要启用外部访问,必须定义外部服务的 ServiceEntry,或者在安装时配置为直接访问外部服务

本文描述了如何在 Istio 中配置出口流量的 TLS

1. 用例


考虑一个对外部站点执行 HTTP 调用的遗留应用程序, 假设运行应用程序的组织收到一个新要求,该要求规定必须加密所有外部流量, 使用 Istio,只需通过配置就可以实现这样的要求,而无需更改应用程序的代码。

在此任务中,如果原始流量为 HTTP,则将 Istio 配置为打开与外部服务的 HTTPS 连接, 应用程序将像以前一样发送未加密的 HTTP 请求,Istio 将加密应用程序的请求。

2. 前提条件


  • 按照安装指南中的说明设置 Istio 。
  • 启动 sleep 示例,它将作为外部调用的测试源。

如果您已启用自动注入 sidecar, 请按如下命令部署 sleep 应用程序:

$ kubectl apply -f samples/sleep/sleep.yaml

否则,您必须在部署 sleep 应用程序之前手动注入 sidecar:

$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml)

请注意,任何可以 execcurl 的 pod 都可以执行以下步骤。

  • 创建一个 shell 变量来保存源 pod 的名称,以便将请求发送到外部服务, 如果您使用 sleep 示例,请按如下命令运行:
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})

3. 配置 HTTP 和 HTTPS 外部服务


首先,与控制出口流量任务相同的方式配置对 cnn.com 的访问。 请注意,在 hosts 中定义中使用 * 通配符:*.cnn.com , 使用通配符可以访问 www.cnn.com 以及 edition.cnn.com 。

1. 创建一个 ServiceEntry 以允许访问外部 HTTP 和 HTTPS 服务:

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: cnn
spec:
  hosts:
  - "*.cnn.com"
  ports:
  - number: 80
    name: http-port
    protocol: HTTP
  - number: 443
    name: https-port
    protocol: HTTPS
  resolution: NONE

2. 向外部 HTTP 服务发出请求:

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics

HTTP/1.1 301 Moved Permanently
...
location: https://edition.cnn.com/politics
...

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
Content-Length: 151654
...

输出应该与上面的类似(一些细节用省略号代替)。

注意 curl 的 -L 标志,它指示 curl 遵循重定向, 在这种情况下, 服务器返回一个重定向响应(301 Moved Permanently)到 http://edition.cnn.com/politics 的 HTTP 请求, 重定向响应指示客户端通过 HTTPS 向 https://edition.cnn.com/politics 发送附加请求, 对于第二个请求,服务器返回所请求的内容和 200 OK 状态代码。

而对于 curl 命令,这种重定向是透明的,这里有两个问题, 第一个问题是冗余的第一个请求,它使获取 http://edition.cnn.com/politics 内容的延迟加倍, 第二个问题是 URL 的路径,在这种情况下是 politics ,以明文形式发送, 如果有攻击者嗅探您的应用程序与 cnn.com 之间的通信,则攻击者会知道您的应用程序获取的 cnn.com 的哪些特定主题和文章, 出于隐私原因,您可能希望阻止攻击者披露此类信息。

在下一节中,我们将通过配置 Istio 执行 TLS 来解决这两个问题, 在继续下一部分之前先清理配置:

$ kubectl delete serviceentry cnn

4. 出口流量的 TLS


总共需要创建三个资源对象,先定义一个 ServiceEntry 以允许网格内部应用程序访问 edition.cnn.com ,然后定义一个 VirtualService 来执行请求端口的重定向,最后定义一个 DestinationRule 用来执行 TLS 发起。

ServiceEntry

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: cnn
spec:
  hosts:
  - edition.cnn.com
  ports:
  - number: 80
    name: http-port
    protocol: HTTP
  - number: 443
    name: http-port-for-tls-origination
    protocol: HTTP
  resolution: DNS
EOF

与上一节中的 ServiceEntry 不同,这里将端口 433 上的协议改为 HTTP,因为客户端将发送 HTTP 请求,而 Istio 将为它们执行 TLS 发起, 此外,在此示例中,必须将解析策略设置为 DNS 才能正确配置 Envoy。

此处 ServiceEntry 与 Envoy 配置文件的映射关系可以参考我之前的文章 控制 Egress 流量 中的 HTTP ServiceEntry 配置深度解析这一部分,具体细节不再赘述。

图 1:ServiceEntry 与 Envoy route 的映射关系

VirtualService

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: rewrite-port-for-edition-cnn-com
spec:
  hosts:
  - edition.cnn.com
  http:
  - match:
      - port: 80
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 443
EOF

通过 图 1 可以看出目的地址是 edition.cnn.com:80 的流量被路由到了 Cluster outbound|80||edition.cnn.com。创建了 VirtualService 之后我们再来看一下这部分的路由:

$ istioctl pc routes $SOURCE_POD --name 80 -o json
...
{
                "name": "edition.cnn.com:80",
                "domains": [
                    "edition.cnn.com",
                    "edition.cnn.com:80"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|443||edition.cnn.com",
                            "timeout": "0.000s",
                            "maxGrpcTimeout": "0.000s"
                        },
                        "decorator": {
                            "operation": "edition.cnn.com:443/*"
                        },
                        ...
                        ...

该 VirtualService 的作用就是将目的地址是 edition.cnn.com:80 的流量重新路由到 Cluster outbound|443||edition.cnn.com,以此来实现访问 80 端口重定向到 443 端口的功能

图 2:ServiceEntry + VirtualService 与 Envoy route 的映射关系

请注意 VirtualService 使用特定的主机 edition.cnn.com (没有通配符),因为 Envoy 代理需要确切地知道使用 HTTPS 访问哪个主机。

但此时我们仍然不能访问外部服务,因为 istio 通过 443 端口发起连接的时候,使用的仍然是 HTTP 协议,具体可以看 Envoy 的配置文件:

$ istioctl pc clusters $SOURCE_POD --fqdn edition.cnn.com --port 443 -o json
[
    {
        "name": "outbound|443||edition.cnn.com",
        "type": "STRICT_DNS",
        "connectTimeout": "1.000s",
        "hosts": [
            {
                "socketAddress": {
                    "address": "edition.cnn.com",
                    "portValue": 443
                }
            }
        ],
        "circuitBreakers": {
            "thresholds": [
                {}
            ]
        },
        "dnsLookupFamily": "V4_ONLY"
    }
]

DestinationRule

现在只剩下最后一步,我们需要让 istio 在出口流量上执行 TLS 发起,使用 HTTPS 协议来访问外部服务。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: originate-tls-for-edition-cnn-com
spec:
  host: edition.cnn.com
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
    portLevelSettings:
    - port:
        number: 443
      tls:
        mode: SIMPLE # initiates HTTPS when accessing edition.cnn.com
EOF

再来看一下 Envoy 的 Cluster 配置:

$ istioctl pc clusters $SOURCE_POD --fqdn edition.cnn.com --port 443 -o json
[
    {
        "name": "outbound|443||edition.cnn.com",
        "type": "STRICT_DNS",
        "connectTimeout": "1.000s",
        "hosts": [
            {
                "socketAddress": {
                    "address": "edition.cnn.com",
                    "portValue": 443
                }
            }
        ],
        "circuitBreakers": {
            "thresholds": [
                {}
            ]
        },
        "tlsContext": {
            "commonTlsContext": {}
        },
        "dnsLookupFamily": "V4_ONLY"
    }
]

创建 DestinationRule 之后,Cluster 配置项中多了一个 tlsContext 字段,该字段用来指定连接到上游群集的 TLS 配置(由于这里是出口流量,所以上游集群在这里指的是外部服务 edition.cnn.com)。如果没有添加该字段,则 Envoy 将不会使用 TLS 发起新连接。具体参考:Envoy 官方文档

现在发送 HTTP 请求到 http://edition.cnn.com/politics ,如上一节所述:

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
Content-Length: 151654
...

这次你会直接收到 200 OK 状态码,因为 Istio 为 curl 执行了 TLS 发起,原始 HTTP 请求会被转化为 HTTPS 转发到 cnn.com,cnn.com 服务器直接返回内容,无需重定向。这就消除了客户端和服务器之间的双重往返,并且请求被 Istio 在网格中加密,而没有暴露出应用程序获取 cnn.com 的 politics 部分这一事实。

请注意,这里使用的命令与上一节中的命令相同,如果你以编程方式访问外部服务的应用程序,配置出口流量的 TLS 之后代码也不需要更改。因此,您可以通过配置 Istio 来获得 TLS 的好处,而无需对代码进行更改。

5. 其他安全因素


请注意,应用程序 pod 与本地主机上的 sidecar 之间的流量仍未加密,这意味着如果攻击者能够穿透应用程序所在的节点,他们仍然可以在该节点的本地网络上看到未加密的通信。在某些环境中,可能存在严格的安全要求,即必须加密所有流量,即使在节点的本地网络上也是如此,如果有这么严格的要求,应用程序应该只使用 HTTPS(TLS),此任务中描述的 TLS 是不够的。

另外还需要注意,即使对于应用程序发起的 HTTPS ,虽然所有 HTTP 详细信息(主机名,路径,标头等)都是加密的,但攻击者可以通过检查服务器名称指示(SNI)得知加密请求中的主机名称,因为在 TLS 握手期间,发送 SNI 字段时是不加密的。使用 HTTPS 可防止攻击者了解特定的主题和文章,但这并不能阻止攻击者发现你访问的是 cnn.com。

6. 清理


  1. 删除创建的 Istio 资源对象:

    $ kubectl delete serviceentry cnn
    $ kubectl delete virtualservice rewrite-port-for-edition-cnn-com
    $ kubectl delete destinationrule originate-tls-for-edition-cnn-com
    
  2. 删除 sleep 服务:

    $ kubectl delete -f samples/sleep/sleep.yaml
    

-----------他日江湖相逢 再当杯酒言欢-----------

「真诚赞赏,手留余香」

杨传胜

真诚赞赏,手留余香

使用微信扫描二维码完成支付

See Also

隐藏