HTTP Egress 流量的监控和访问策略管理
原文:Monitoring and Access Policies for HTTP Egress Traffic
作者:VADIM EISENBERG 和 RONEN SCHAFFER
Istio 的主要功能就是在服务网格内部管理微服务之间的通信,除此之外,Istio 还能对 Ingress(从外部进入网格) 和 Egress(从网格发出到外部) 流量进行管理。不管是网格内部流量,还是 Ingress 或者 Egress 流量,Istio 都能够在其中进行访问策略的控制,并完成遥测数据的聚合工作。
本文中我们会展示如何使用 Istio 在 HTTP Egress 流量中实施监控和访问策略控制。文中谈到的内容针对 Istio 0.8.0 及以上是有效的。
用例
假设一个组织正在运行的应用需要处理来自于 cnn.com
的内容。这些应用已经被解构为部署在 Istio 服务网格中的微服务。这些应用要获取 cnn.com
多个频道的内容:edition.cnn.com/politics、edition.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 的(未授权)访问。
该组织决定防止对新策略发生篡改,通过技术手段执行策略,防止恶意应用访问受限内容。
相关任务
- Egress 流量控制:网格内应用对(Kubernetes 集群)外部的 HTTP 和 HTTPS 服务的访问方式。
- TLS 方式访问 Egress 流量:内部应用使用 HTTP 协议访问需要 HTTPS 的外部服务的能力。
- 配置 Egress Gateway :如何配置 Istio 令其使用独立的
egress gateway
网关服务来发送 Egress 流量。 - 收集指标和日志:为网格中的服务配置指标和日志。
- 使用 Grafana 进行指标可视化:Istio Dashboard 在网格流量监控方面的作用。
- 基础访问控制:网格内服务的访问控制问题。
- 安全访问控制:如何使用黑名单和白名单配置访问策略。
和上面列出的任务不同,本文讲述的是 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 一文。下面的示意图中包含了刚才讲到的这些实体和依赖关系:
创建
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
分别向 edition.cnn.com/politics、edition.cnn.com/sport 发送请求,所有请求都应该返回
200 OK
。$ 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/sport 和 edition.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 策略来实现。
用之前的 Configure an Egress Gateway 中的版本替换
edition.cnn.com
的VertualService
: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
对日志和访问策略同样生效。
定义
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
再次执行 curl 指令,发现 edition.cnn.com/politics 返回了 404:
$ 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 流量监控,也能增强安全性。
- 浏览使用 Grafana 进行指标可视化任务,完成其中的步骤 1-3。
从
$SOURCE_POD
发送到cnn.com
的请求:$ 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
一节。会看到大致如下的显示:
在左侧 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 解密之后变成明文的阶段。
这种方式的缺陷在于,请求在本机是明文传输的,可能会违反某些组织的安全需要。有些 SDK 的外部服务 URL 包含协议部分都是硬编码的,因此发送 HTTP 请求是不可能的。这种办法的好处是可以获取 HTTP 头、方法以及 URL 路径,并可以据此制定规则。
在 HTTPS 形式下,从应用到外部目标的请求是端到端加密的。下图演示了这种方式的数据流。在 Gateway 中见到的报文,同样还是 HTTPS。
端到端的 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 过程的优劣,同时也就有不同的控制能力。
清理
- 执行 配置 Egress Gateway 任务中的 清理任务。
删除日志和策略配置:
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
删除
politics
命名空间执行使用 Grafana 进行指标可视化任务中的清理环节。