HTTP Egress 流量的监控和访问策略管理

  • HTTP Egress 流量的监控和访问策略管理已关闭评论
  • 12 views
  • A+
所属分类:Kubernetes

Istio 的主要功能就是在服务网格内部管理微服务之间的通信,除此之外,Istio 还能对 Ingress(从外部进入网格) 和 Egress(从网格发出到外部) 流量进行管理。不管是网格内部流量,还是 Ingress 或者 Egress 流量,Istio 都能够在其中进行访问策略的控制,并完成遥测数据的聚合工作。

本文中我们会展示如何使用 Istio 在 HTTP Egress 流量中实施监控和访问策略控制。文中谈到的内容针对 Istio 0.8.0 及以上是有效的。

用例

假设一个组织正在运行的应用需要处理来自于 cnn.com 的内容。这些应用已经被解构为部署在 Istio 服务网格中的微服务。这些应用要获取 cnn.com 多个频道的内容:edition.cnn.com/politicsedition.cnn.com/sport 以及edition.cnn.com/health。目前已经配置 Istio 使其允许访问edition.cnn.com,一切运行良好。然而在某一天,他们决定限制政治方面的内容,技术上讲,就是要阻止对 edition.cnn.com/politics 的访问,继续允许对edition.cnn.com/sport 以及 edition.cnn.com/health 的访问。对edition.cnn.com/politics 的访问需要在应用程序、命名空间以及用户的不同粒度上进行访问控制。

要实现这个目标,运维人员需要监控对外部服务的访问,并分析 Istio 日志来确保对 edition.cnn.com/politics 的访问都是经过授权的。另外他们还要配置 Istio,让 Istio 自动阻止对 edition.cnn.com/politics 的(未授权)访问。

该组织决定防止对新策略发生篡改,通过技术手段执行策略,防止恶意应用访问受限内容。

相关任务

和上面列出的任务不同,本文讲述的是 Istio 对 Egress 流量的监控和访问策略。

开始之前

依照 配置 Egress Gateway,使用 Egress Gateway 执行 TLS 访问 任务中的步骤,不要执行清理操作。完成之后,就可在网格之内使用安装了 curl 的容器来访问 edition.cnn.com/politics 了。下面的内容中,我们假设名为 SOURCE_POD的环境变量中包含了 Pod 名称。

配置监控和访问策略

既然要用安全方式来完成任务,就需要通过 egress gateway 来进行 egress 传输,这部分内容在 配置 Egress Gateway 中有详细描述。这里所谓的安全方式指的是防止恶意应用绕过 Istio 监控和策略管理进行未经授权的访问。

我们的场景中,该组织执行了上一节“开始之前”的步骤。这个步骤完成后,开放了对 edition.cnn.com 的访问,并且配置了响应的 Egress Gateway。现在可以对edition.cnn.com 进行监控和策略进行配置了。

日志

首先配置一下对 *.cnn.com 的记录。创建一个 logentry 和两个 stdio 类型的handler,其中一个用 error 级别的日志来记录受限的访问,另外一个用 info级别来记录所有到 *.cnn.com 的访问。接下来创建 rules 把 logentry 定向到handler 上。对 *.cnn.com/politics 的访问会被指派给受限访问的规则,剩余的访问则会被另一条规则接收。要理解 Istio 的 logentries、rules 以及handlers,可以阅读 Istio Adaper Model 一文。下面的示意图中包含了刚才讲到的这些实体和依赖关系:

HTTP Egress 流量的监控和访问策略管理

  • 创建 logentries、rules 以及 handlers:
# egress 访问的日志条目
apiVersion: "config.istio.io/v1alpha2"
kind: logentry
metadata:
  name: egress-access
  namespace: istio-system
spec:
  severity: '"info"'
  timestamp: request.time
  variables:
    destination: request.host | "unknown"
    path: request.path | "unknown"
    source: source.labels["app"] | source.service | "unknown"
    sourceNamespace: source.namespace | "unknown"
    user: source.user | "unknown"
    responseCode: response.code | 0
    responseSize: response.size | 0
  monitored_resource_type: '"UNSPECIFIED"'
---
# Handler for error egress access entries
apiVersion: "config.istio.io/v1alpha2"
kind: stdio
metadata:
  name: egress-error-logger
  namespace: istio-system
spec:
  severity_levels:
    info: 2 # 输出级别为 error
  outputAsJson: true
---
# 访问 *.cnn.com/politics 的规则
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: handle-politics
  namespace: istio-system
spec:
  match: request.host.endsWith("cnn.com") && request.path.startsWith("/politics")
  actions:
  - handler: egress-error-logger.stdio
    instances:
    - egress-access.logentry
---
# Info 级别的 Egress 日志
apiVersion: "config.istio.io/v1alpha2"
kind: stdio
metadata:
  name: egress-access-logger
  namespace: istio-system
spec:
  severity_levels:
    info: 0 # 输出为 Info 级别
  outputAsJson: true
---
# 访问 *.cnn.com 的规则
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: handle-cnn-access
  namespace: istio-system
spec:
  match: request.host.endsWith(".cnn.com")
  actions:
  - handler: egress-access-logger.stdio
    instances:
    - egress-access.logentry
$ kubectl exec -it $SOURCE_POD -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
200
200
200
  • 查询 Mixer 的日志,查看日志中出现的请求信息:
kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep egress-access | grep cnn | tail -4

返回如下信息:

{"level":"info","time":"2018-06-18T13:22:58.317448Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":200,"responseSize":150448,"source":"sleep","user":"unknown"}
{"level":"error","time":"2018-06-18T13:22:58.317448Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":200,"responseSize":150448,"source":"sleep","user":"unknown"}
{"level":"error","time":"2018-06-18T13:22:58.317448Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":200,"responseSize":150448,"source":"sleep","user":"unknown"}
{"level":"info","time":"2018-06-18T13:22:59.354943Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/health","responseCode":200,"responseSize":332218,"source":"sleep","user":"unknown"}

会看到关于三个请求的四条日志。三个 info 级别的条目是关于 edition.cnn.com的,一条 error 级别的条目就是关于 edition.cnn.com/politics 的访问。服务网格的运维人员能看到所有的访问情况,并且也能通过对 error 日志的搜索来查找受限访问。这是在禁止访问之前的第一个监控措施,把所有受限访问都作为错误记录下来,在某些情况下,这就足够安全了。

利用路由进行访问控制

在启动对 edition.cnn.com 的访问日志之后,自动启动了一个访问策略,只允许访问 /health 和 /sport URL。这样简单的策略控制可以用 Istio 路由来实现。

  • 重新定义 edition.cnn.com 的 VirtualService:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: direct-through-egress-gateway
spec:
  hosts:
  - edition.cnn.com
  gateways:
  - istio-egressgateway
  - mesh
  http:
  - match:
    - gateways:
      - mesh
      port: 80
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        port:
          number: 443
      weight: 100
  - match:
    - gateways:
      - istio-egressgateway
      port: 443
      uri:
        regex: "^.health|^.sport"
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 443
      weight: 100

注意这里加入了一个针对 uri 的 match 条件,会检查 URL 路径是不是 /health或者 /sport。另外还要注意的是,这个条件是加入到 VirtualService 的 istio-egressgateway 部分,egress gateway 是一个需要注意安全的组件(参见 Egress Gateway 的安全考量),应该慎重对待其安全性,防止影响后续的策略实施过程。

  • 再次发送之前的三个 HTTP 请求:
$ kubectl exec -it $SOURCE_POD -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
404
200
200

会看到 edition.cnn.com/politics 返回了 404 Not Found,edition.cnn.com/sportedition.cnn.com/health 都返回了 200。

VirtualService 的传播和生效可能需要几秒钟的等待。

  • 查询 Mixer 日志,看看刚才发生的请求在日志中的体现:
kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep egress-access | grep cnn | tail -4

得到结果如下:

{"level":"info","time":"2018-06-19T12:39:48.050666Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":404,"responseSize":0,"source":"sleep","sourceNamespace":"default","user":"unknown"}
{"level":"error","time":"2018-06-19T12:39:48.050666Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":404,"responseSize":0,"source":"sleep","sourceNamespace":"default","user":"unknown"}
{"level":"info","time":"2018-06-19T12:39:48.091268Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/health","responseCode":200,"responseSize":334027,"source":"sleep","sourceNamespace":"default","user":"unknown"}
{"level":"info","time":"2018-06-19T12:39:48.063812Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/sport","responseCode":200,"responseSize":355267,"source":"sleep","sourceNamespace":"default","user":"unknown"}

这里还能看到 edition.cnn.com/politics 的访问日志,只不过这次的responseCode 是 404。

使用 Istio 路由之后,我们成功的实现了初步的访问控制,但是如果是更复杂的需要,这种程度还是不够的。例如希望允许特定条件下对edition.cnn.com/politics 的访问,这需要一些更复杂的策略,只判断 URL 是不够的。这就需要 Istio Mixer Adapter,(例如白名单黑名单)的协助,来协助控制对 URL 路径的允许和禁止行为。借 [Policy Rules] 的帮助,这样就可以使用 Istio expression language 来实现复杂条件的定义,完成包含逻辑控制在内的复杂限制了。这些规则可以在日志和策略检查之间进行复用。另外还可以使用Istio RBAC进行更复杂的控制。

还有一个额外的需要就是和远程访问策略系统进行集成。如果用例中设计的组织已经有使用一些 认证和访问管理系统,可能会需要配置 Istio 从这些系统中获取访问策略方面的信息。可以通过实现 Istio Mixer Adapter 的方式来进行集成。

取消掉前面我们使用路由规则实现的访问控制,接下来使用 Mixer 策略来实现。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: direct-through-egress-gateway
spec:
  hosts:
  - edition.cnn.com
  gateways:
  - istio-egressgateway
  - mesh
  http:
  - match:
    - gateways:
      - mesh
      port: 80
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        port:
          number: 443
      weight: 100
  - match:
    - gateways:
      - istio-egressgateway
      port: 443
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 443
      weight: 100
  • 再次发送之前的三个到 cnn.com 的请求,这里会看到三个 200 的成功返回:
$ kubectl exec -it $SOURCE_POD -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
200
200
200

VirtualService 的传播和生效可能需要几秒钟的等待。

使用 Mixer 策略进行访问控制

这一步中,我们使用 Listchecker adapter,这是白名单的一个变体。用一个静态 URL 列表定义一个 listentry,然后在 listchecker 中用 overrides 字段进行检查。如果有外部的 认证和访问管理系统,可以使用 providerurl 字段取而代之。下面图示显示了更新之后的 对象关系。注意这里复用了同样的策略,handle-cnn-access 对日志和访问策略同样生效。

HTTP Egress 流量的监控和访问策略管理

  • 定义 path-checker 以及 request-path:
apiVersion: "config.istio.io/v1alpha2"
kind: listchecker
metadata:
  name: path-checker
  namespace: istio-system
spec:
  overrides: ["/health", "/sport"]  # 提供一个静态列表
  blacklist: false
---
apiVersion: "config.istio.io/v1alpha2"
kind: listentry
metadata:
  name: request-path
  namespace: istio-system
spec:
  value: request.path
  • 修改 handle-cnn-access 规则,要求将 request-path 发送给 path-checker:
# 访问 cnn.com egress 的规则
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: handle-cnn-access
  namespace: istio-system
spec:
  match: request.host.endsWith(".cnn.com")
  actions:
  - handler: egress-access-logger.stdio
    instances:
      - egress-access.logentry
  - handler: path-checker.listchecker
    instances:
      - request-path.listentry
$ kubectl exec -it $SOURCE_POD -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
404
200
200

使用 Mixer 策略进行访问控制(续)

在前面我们配置了日志和访问控制之后,新增一个需要就是允许在 policics 命名空间内的应用能够访问 cnn.com 的所有内容,并且不受监控。接下来我们在 Istio 中进行配置,完成这一要求。

  • 创建 polictics 命名空间
$ kubectl create namespace politics
namespace "politics" created
  • 在 polictics 命名空间中启动 sleep

如果使用了 自动注入 Sidecar,执行:$ kubectl apply -n politics -f samples/sleep/sleep.yaml,否则,就需要进行注入了:kubectl apply -n politics -f <(istioctl kube-inject -f samples/sleep/sleep.yaml)。

  • 预备使用 policics 命名空间中的 sleep pod 来发送请求,这里定义一个环境变量来保存 Pod 名称。
export SOURCE_POD_IN_POLITICS=$(kubectl get pod -n politics -l app=sleep -o jsonpath={.items..metadata.name})
  • 这次从新的 Pod($SOURCE_POD_IN_POLITICS)中发送刚才的请求。因为我们还没有给新的命名空间中的应用设置例外,edition.cnn.com/politics 还是返回了 404。
$ kubectl exec -it $SOURCE_POD_IN_POLITICS -n politics -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
404
200
200
  • 查询 Mixer 日志,会看到来自 politics 命名空间的访问记录:
kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep egress-access | grep cnn | tail -4

查询结果:

{"level":"info","time":"2018-06-19T17:37:14.639102Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":404,"responseSize":76,"source":"sleep","sourceNamespace":"politics","user":"unknown"}
{"level":"error","time":"2018-06-19T17:37:14.639102Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/politics","responseCode":404,"responseSize":76,"source":"sleep","sourceNamespace":"politics","user":"unknown"}
{"level":"info","time":"2018-06-19T17:37:14.653225Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/sport","responseCode":200,"responseSize":356349,"source":"sleep","sourceNamespace":"politics","user":"unknown"}
{"level":"info","time":"2018-06-19T17:37:14.767923Z","instance":"egress-access.logentry.istio-system","destination":"edition.cnn.com","path":"/health","responseCode":200,"responseSize":334027,"source":"sleep","sourceNamespace":"politics","user":"unknown"}

上面的输出中可以看到 sourceNamespace 的值为 politics。

  • 重新定义 handle-cnn-access 以及 handle-politics 策略,为新的命名空间定义例外的日志和访问策略。
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: handle-politics
  namespace: istio-system
spec:
  match: request.host.endsWith("cnn.com") && request.path.startsWith("/politics") && source.namespace != "politics"
  actions:
  - handler: egress-error-logger.stdio
    instances:
    - egress-access.logentry
---
# 访问 egress cnn.com 的规则
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: handle-cnn-access
  namespace: istio-system
spec:
  match: request.host.endsWith(".cnn.com") && source.namespace != "politics"
  actions:
  - handler: egress-access-logger.stdio
    instances:
      - egress-access.logentry
  - handler: path-checker.listchecker
    instances:
      - request-path.listentry
  • 在 $SOURCE_POD 中重复执行测试:
$ kubectl exec -it $SOURCE_POD -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
404
200

$SOURCE_POD 是在 default 命名空间的,所以对 edition.cnn.com/politics 的访问会被拒绝。

  • 在 $SOURCE_POD_IN_POLITICS 中重复执行测试:
$ kubectl exec -it $SOURCE_POD_IN_POLITICS -n politics -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
200
200
200

现在所有访问都可以通过了。

  • 查看 Mixer 日志,会发现看不到 sourceNamespace 为 politics 的条目了:
kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep egress-access | grep cnn

Dashboard

让运维人员能够可视化的进行 egress 流量监控,也能增强安全性。

$ kubectl exec -it $SOURCE_POD -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
404
200
200

因为 $SOURCE_POD 是存在于 default 命名空间中的,所以对edition.cnn.com/politics 的访问会被拒绝。

  • 从 $SOURCE_POD_IN_POLITICS 发送到 cnn.com 的请求:
$ kubectl exec -it $SOURCE_POD_IN_POLITICS -n politics -c sleep -- bash -c 'curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/politics; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/sport; curl -sL -o /dev/null -w "%{http_code}\n" http://edition.cnn.com/health'
200
200
200
  • 滚动 Dashboard 到 HTTP 服务部分的 istio-egressgateway.istio-system.svc.cluster.local 一节。会看到大致如下的显示:

HTTP Egress 流量的监控和访问策略管理

在左侧 Requests by Source, Version and Response Code 中,会看到 default 命名空间中的 unknown 版本的 sleep 应用收到的 404 返回码。运维人员可以据此发现试图访问受控目标的应用。还可以看到在 polictics 命名空间中 sleep 应用收到的 200 返回码,这样也就知道了对受控外部资源的有效访问情况。

和 HTTPS egress 控制的比较

这个用例中,应用使用的是 HTTP 和 Istio Egress Gateway 结合提供 TLS 的。换个方式,应用可以自行发送 TLS 请求给 edition.cnn.com,本节中我们会对两种方式的优劣进行一些比较。

HTTP 方式中,请求在本地是明文传输,经由 Sidecar 转发给 Egress Gateway 的。如果 Istio 使用双向 TLS 部署,Sidecar 代理和 Egress Gateway 之间的通信就是加密的。Egress Gateway 解密信息,查看 URL 路径,HTTP 方法和 Header,上报监控数据、执行前置检查。如果请求没有被拒绝,Egress Gateway 就会为外部目标执行 TLS 封装,这样请求就会被再次加密,以密文形式发送给外部目标。下图演示了这种方式中的网络流向。图中 Gateway 方块中的 HTTP 标志,代表报文在 Gateway 解密之后变成明文的阶段。

HTTP Egress 流量的监控和访问策略管理

这种方式的缺陷在于,请求在本机是明文传输的,可能会违反某些组织的安全需要。有些 SDK 的外部服务 URL 包含协议部分都是硬编码的,因此发送 HTTP 请求是不可能的。这种办法的好处是可以获取 HTTP 头、方法以及 URL 路径,并可以据此制定规则。

在 HTTPS 形式下,从应用到外部目标的请求是端到端加密的。下图演示了这种方式的数据流。在 Gateway 中见到的报文,同样还是 HTTPS。

HTTP Egress 流量的监控和访问策略管理

端到端的 HTTPS 可能是更好的一种加密方式。然而因为流量是加密通过 Istio 代理和 Egress Gateway 的,因此只能看到源和目的的 IP 以及 SNI。在 Istio 开通双向 TLS 的情况下,源身份也是可知的。Gateway 无法获知 HTTP 头、方法以及 URL 路径,因此基于 HTTP 信息的策略就无法实现了。我们的用例中要求可以访问 edition.cnn.com。如果 Istio 中启用了双向 TLS,组织可以设置部分应用允许访问 edition.cnn.com。然而却无法允许或禁止访问特定的 URL。对/politics 的允许或者禁止,在这种上下文中都是无法实现的。

我们认为,这样讲解之后,用户就可以对这两种方法进行优劣势的评估,进而做出合适的选择。

结论

本文中我们展示了 Istio 用 HTTP 访问 Egress 时的监控和策略。其中的监控过程,可以配置日志适配器结合 Istio Dashboard 来完成。而访问策略可以通过配置 VirtualService 或者配置多种策略适配器来完成。我们演示了一个简单的策略,只允许某些 URL 的访问。我们另外还展示了稍微复杂一些的策略,通过放行指定命名空间中的指定应用,来做出例外。最后,我们比较了两种 HTTPS 过程的优劣,同时也就有不同的控制能力。

清理

  1. 执行 配置 Egress Gateway 任务中的 清理任务。
  2. 删除日志和策略配置:
    kubectl delete logentry egress-access -n istio-system
    kubectl delete stdio egress-error-logger -n istio-system
    kubectl delete stdio egress-access-logger -n istio-system
    kubectl delete rule handle-politics -n istio-system
    kubectl delete rule handle-cnn-access -n istio-system
    kubectl delete -n istio-system listchecker path-checker
    kubectl delete -n istio-system listentry request-path
    
  3. 删除 politics 命名空间
  4. 执行使用 Grafana 进行指标可视化任务中的清理环节。
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 微信公众号
  • 微信公众号扫一扫
  • weinxin
avatar