全书目录
- 第一章 概述
- 第二章 安装
- 第三章 流控
- 第四章 服务弹性
- 第五章 混沌测试
- 第六章 可观测性
本文目录
第7章 安全
7.1 身份认证
7.1.1 Kubernetes上的Istio的身份认证方案
7.1.2 mTLS(双向TLS)
7.2 访问授权 - 基于角色的访问控制(RBAC)
7.3 访问策略 - 通过Mixer Policy进行访问控制
7.4 结论
第7章 安全
越来越多的现代云原生应用开发体系中都会有好几个独立的开发团队,每个团队采用不同的开发迭代周期,新功能以周或天为单位迭代上线,只对他们自己的任务负责。Istio的使命是从组成整个应用的所有微服务层面,确保一定程度的连续性。Isitio的关键能力之一是实施安全约束,同时对每个服务的逻辑代码透明。有了Istio代理后,你就可以在服务之间的网络层面施加这些约束。
将单一应用程序分解为微服务可提供各种好处,包括更好的灵活性、可伸缩性以及服务复用的能力。但是,微服务也有特殊的安全需求:
· 为了抵御中间人攻击,需要流量加密。
· 为了提供灵活的服务访问控制,需要双向TLS和细粒度的访问策略。
· 要审核谁在什么时候做了什么,需要审计工具。
Istio Security 尝试提供全面的安全解决方案来解决所有这些问题。Istio 安全功能提供强大的身份、强大的策略、透明的TLS加密以及用于保护您的服务和数据的身份验证,授权和审计(AAA)工具,如图7-1所示。Istio 安全的目标是:
· 默认安全: 应用程序代码和基础结构无需更改
· 深度防御: 与现有安全系统集成,提供多层防御
· 零信任网络: 在不受信任的网络上构建安全解决方案
图7-1. Istio安全概述
Istio 中的安全性涉及多个组件,其总体架构如图7-2所示:
· Citadel 用于密钥和证书管理
· Envory用于实现客户端和服务器之间的安全通信
· Pilot 将授权策略和安全命名信息分发给代理
· Mixer 管理授权和审计
图7-2. Istio安全架构
即使本书中用到的非常简单的示例应用,其中customer微服务调用preference微服务,它又调用recommendation微服务,还是有好几个地方需要应用这些安全约束。
本章中,我们会介绍Istio的基于mTLS的身份认证、基于Mixer Policy的策略控制,以及基于RBAC的授权管控。
7.1 身份认证
7.1.1 Kubernetes上的Istio的身份认证方案
身份是任何安全基础架构的基本概念。在服务间通信开始时,双方必须互相交换身份信息凭证以进行相互的身份认证。
在Istio中,客户端根据安全命名信息(secure naming information)来检查服务器端的身份标识,并查看它是否是该服务的授权运行者;服务器端通过授权策略(authentication policy)来确定客户端可访问什么,审核谁在什么时候访问了什么,并进行计费等。不同平台上的Istio采用不同的服务标识。在Kubernetes上Istio采用服务账户(service account)作为身份标识,它使用X.509证书来携带SPEFFE格式的身份,X.509证书中的URI字段格式为spiffe://<domain>/ns/<namespace>/sa/<serviceaccount>。
创建和管理身份标识的基本流程:
- Istio Citadel 组件监视Kubernetes api-server,为每个现有和新的服务帐户创建SPIFFE 证书和密钥对。Citadel将证书和密钥对存储为 Kubernetes secret。
- 创建 pod 时,Kubernetes会根据其服务帐户通过 Kubernetes secret volume 将证书和密钥对挂载到 pod 上。
- Citadel 监视每个证书的生命周期,并通过重写 Kubernetes secret自动轮换证书。
- Pilot 生成安全命名信息,该信息定义了哪些服务帐户可以运行哪些服务,然后将安全命名信息传递给Envoy 代理。
Istio 提供两种类型的身份验证:
- 传输身份验证(transport authentication),也称为服务间身份验证:验证建立连接的直接客户端。 Istio 提供双向TLS 作为传输身份验证的完整解决方案。 您可以轻松启用此功能,而无需更改服务代码。这个解决方案:
- 为每个服务提供强大的身份,表示其角色,以实现跨群集和云的互操作性。
- 保护服务到服务通信和最终用户到服务的通信。
- 提供密钥管理系统,以自动执行密钥和证书生成,分发和轮换。
- 来源身份认证(origin authentication),也称为最终用户身份验证:验证作为最终用户或设备发出请求的原始客户端。Istio 通过 JSON Web Token(JWT)验证和 ORY Hydra、Keycloak、Auth0、Firebase Auth、Google Auth 和自定义身份验证来简化开发人员体验,并且轻松实现请求级别的身份验证。
在这两种情况下,Istio都通过自定义Kubernetes API将身份认证策略存储在Istio配置存储中。Pilot会在适当的时候为每个代理保持最新状态以及密钥。
7.1.2 mTLS(双向TLS)
传输层安全性协议(Transport Layer Security,缩写TLS)及其前身安全套接层(Secure Sockets Layer,缩写SSL)是一种安全协议,目的是为互联网通信提供安全及数据安全性保障。Netscape公司在1994年推出HTTPS协议,以SSL进行加密,这是SSL的起源。SSL包括1.0,2.0和3.0等三个版本。IETF(Internet Engineering Task Force,互联网工程任务组)将SSL标准化,并将其称为TLS。从技术上讲,TLS 1.0与SSL 3.0的差异非常微小,目前TLS已发展到了1.3版本。默认地,TLS协议只通过X.509证书向客户端证明服务器端的身份,而向服务器端证明客户端的身份这事情则留给应用自己去做。Mutual TLS(mutual Transport Layer Security,双向TLS,简称mTLS)则能让两端都能证明对端的身份,因此能保障通信在两个方向上都是安全和可信的。mTLS是TLS协议的一种补充和增强。
在Istio服务网格中,mTLS在有注入边车Istio代理的两个微服务之间启用加密通信。默认地,示例程序中的三个服务之间的通信是明文的,只使用了HTTP协议。这意味着,其它能访问你集群的团队,可部署一个自己的服务,然后抓取你应用之间的通信数据。要做到这一点,只需要打开两个终端,一个运行tcpdump,另一个运行curl命令。
终端1:
代码语言:javascript复制 PREFPOD=$(oc get pod -n tutorial -l app=preference -o
'jsonpath={.items[0].metadata.name}')
oc exec -it $PREFPOD -n tutorial -c istio-proxy /bin/bash
sudo tcpdump -A -s 0 'tcp port 8080 and (((ip[2:2]-((ip[0]&0xf)<<2))
((tcp[12]&0xf0)>>2))!= 0)'
终端2:
代码语言:javascript复制 PREFPOD=$(oc get pod -n tutorial -l app=preference -o
'jsonpath={.items[0].metadata.name}')
oc exec -it $PREFPOD -n tutorial -c preference /bin/bash
curl recommendation:8080
然后在终端1中你会看到以下输出:
代码语言:javascript复制 ..:...:.HTTP/1.1 200 OK
content-length: 47
x-envoy-upstream-service-time: 0
date: Mon, 24 Dec 2018 17:16:13 GMT
server: envoy
recommendation v1 from '66b7c9779c-75fpl': 345
在终端2中你会看到:
代码语言:javascript复制 recommendation v1 from '66b7c9779c-75fpl': 345
curl命令正常输出,同时在tcpdump命令的输出中能看到明文信息,如图7-3所示:
图7-3. 未使用mTLS时的三个终端的输出
要在Istio中启用mTLS,只需要使用Policy和DestionationRule对象。Policy定义如下:
代码语言:javascript复制 apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "default"
namespace: "tutorial"
spec:
peers:
- mtls: {}
该策略会被应用到tutorial命名空间内的所有服务上。你还可以设置spec.peers.mtls.mode字段值为“PERMISSIVE”(宽容)而不是“STRICT”(严格),这会允许服务能同时支持mTLS和非mTLS通信,从而兼容那些还没有采用Istio边车代理的服务。
我们的三个服务都已经被注入了Istio边车代理,因此我们可以在Tutorial命名空间上应用mTLS。
在第三个终端中,应用该Policy声明:
代码语言:javascript复制 oc apply -n tutorial -f istiofiles/authentication-enable-tls.yml
现在,定义DestionationRule声明,它在tutorial命名空间的服务间启用mTLS:
代码语言:javascript复制 apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "default"
namespace: "tutorial"
spec:
host: "*.tutorial.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
应用它:
代码语言:javascript复制 oc apply -n tutorial -f istiofiles/destination-rule-tls.yml
然后你在终端2中再次运行curl命令,结果如图7-4所示:
图7-4.启用了mTLS后的三个终端
你看到了tcpdump终端中再也不输出明文信息了,同时curl命令还是成功执行了。你还可以使用istioctl命令去验证这三个服务的mTLS是否启用成功:
代码语言:javascript复制istioctl authn tls-check customer -n tutorial
istioctl authn tls-check preference -n tutorial
istioctl authn tls-check recommendation -n tutorial
现在我们从集群外面进行测试。在终端2中,从preference容器中退出回到宿主机上。使用curl访问customer服务端点,你会收到“Empty reply from server”返回:
代码语言:javascript复制 curl customer-tutorial.$(minishift ip).nip.io
curl: (52) Empty reply from server
上面用到的customer服务URL是由OpenShift Route产生的。在启用了mTLS后,你需要利用一个网关来获得端到端的加密通信。Istio有它自己的入口网关,名为Istio Gateway,它暴露URL给到网格外面,支持Istio的监控、流控和策略等功能。
在Istio中,Gateway实际上是一个运行在网格边缘的提供L4-L6负载均衡能力的负载均衡器,负责接收进入网格或出网格的HTTP/TCP流量,控制着网格边缘的服务暴露。网关根据流入流出方向分为Ingress Gateway(入口网关)和Egress Gateway(出口网关)。前者控制从外部进入网格的访问,配合VirtualService使用;后者控制从网格内访问外部服务,配合DestionationRule和ServiceEntry使用。它还支持对外的mTLS。Gateway和普通边车代理一样都使用Envoy作为代理来进行流量控制。外部访问流量进入网格再出网格的路径如图7-2所示。
要为customer服务设置Istio网关,需创建Gateway以及VirtualService声明:
代码语言:javascript复制 apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: customer-gateway
namespace: tutorial
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http2
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: customer
namespace: tutorial
spec:
hosts:
- "*"
gateways:
- customer-gateway
http:
- match:
- uri:
exact: /
route:
- destination:
host: customer
port:
number: 8080
然后应用这个声明:
代码语言:javascript复制oc apply -f istiofiles/gateway-customer.yml
上面这个Gateway声明暴露了名为“http2”的80端口,允许HTTP协议(备注:80端口在Ingress Gateway服务被安装时就被配置了且名为“http2”,因此,实质上,这个Gateway对象只是重用这个已有的端口。如果要使用新端口的话,则需要修改istio-ingressgateway服务以增加新的端口然后再使用它)。这个对象会被应用到由spec.selector字段指定的pod上,在当前环境中即istio-system命名空间中的istio-ingressgateway pod。然后,Istio会配置这个pod中的代理(Envory)在80端口上开始监听,准备接收发过来的请求。这个端口会被映射为一个Kubernetes集群的节点端口(NodePort),用于从集群外访问。上面这个VirtualService对象会被绑定到由spec. gateways字段指定的Gateway对象上,它负责控制Gateway收到请求后的转发目标,其由route.desitionaion字段指定,本例中为customer:80。
前面说过,Istio Gateway服务为这个Gateway对象暴露了一个NodePort,用于从集群外通过该端口访问VirtualService对象所指向的服务。该端口绑定在minishift或minikube的IP地址上。通过下面的命令获得这个NodePort端口号,然后拼装成网关URL:
代码语言:javascript复制 INGRESS_PORT=$(oc -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
GATEWAY_URL=$(minishift ip):$INGRESS_PORT
curl http://${GATEWAY_URL}/
通过网关URL发起一个curl命令,输出正常,如图7-5所示:
图7-5.使用Istio customer 网关后的三个终端
清理环境:
代码语言:javascript复制 oc delete -n tutorial -f istiofiles/gateway-customer.yml
oc delete -n tutorial -f istiofiles/destination-rule-tls.yml
oc delete -n tutorial -f istiofiles/authentication-enable-tls.yml
回到最初的通过OpenShift Route暴露出的customer服务的地址:
代码语言:javascript复制oc expose service customer
curl customer-tutorial.$(minishift ip).nip.io
小结一下,客户端通过双向TLS调用服务端的主要流程如下:
- Istio 将出站流量从客户端重新路由到客户端的本地Envoy代理。
- 客户端 Envoy与服务器端Envoy开始双向TLS握手。在握手期间,客户端Envoy还从服务器端的X.509证书中获取服务账户名称,并与Pilot已下发的安全命名信息数据进行比对,以进行安全命名检查,以验证服务器证书中显示的服务帐户是否被授权运行到目标服务。
- 客户端Envoy和服务器端Envoy建立了一个双向的TLS连接,Istio将流量从客户端 Envoy转发到服务器端Envoy。
- 服务器端对客户端进行访问授权检查,服务器端Envoy代理从客户端的X.509证书中获取服务账户名称,并与已有授权策略核对,以确定客户端有访问服务器端功能的权限。确认后,服务器端Envoy通过本地TCP连接将流量转发到服务器服务。
客户端通过双向TLS调用服务端的过程中,服务器端会对客户端进行授权检查。Istio提供RBAC认证功能,用于定义哪些服务可以被哪些用户访问;还提供Mixer Policy,用于定义服务的访问控制列表(ACL)。
7.2 访问授权 - 基于角色的访问控制(RBAC)
Istio RBAC定义了ServiceRole和ServiceRoleBinding两个类型。ServiceRole定义了一组规则(rule),每条规则包括service(目标服务)、method(访问服务的方法)、path(HTTP路径或gRPC方法)等字段。ServiceRoleBinding对象则包括指向某ServiceRole对象的roleRef,以及一组实体(subject,比如用户),用于向实体分配ServiceRole对象所定义的访问权限。
请注意,使用Istio RBAC之前要启用mTLS,因为服务器端要利用mTLS获取客户端的身份信息。
首先确保你的环境中没有以下对象:
代码语言:javascript复制 oc get destinationrule -n tutorial
oc get virtualservice -n tutorial
oc get gateway -n tutorial
oc get policy -n tutorial
启用Istio RBAC支持很简单,只需要使用RbacConfig对象:
代码语言:javascript复制 apiVersion: "rbac.istio.io/v1alpha1"
kind: RbacConfig
metadata:
name: default
spec:
mode: 'ON_WITH_INCLUSION'
inclusion:
namespaces: ["tutorial"]
其中mode可以是以下值:
- OFF:禁用Istio RBAC认证
- ON:对网格中的所有服务启用Istio RBAC认证
- ON_WITH_INCLUSION:只对inclusion字段指定的服务和命名空间启用Istio RBAC认证
- ON_WITH_EXCLUSION:对除了由exclusion字段指定的服务和命名空间外的所有服务启用Istio RBAC认证
创建RbacConfig对象:
代码语言:javascript复制 oc create -f istiofiles/authorization-enable-rbac.yml -n tutorial
现在,用curl访问customer服务端点,你会收到如下错误:
代码语言:javascript复制 curl customer-tutorial.$(minishift ip).nip.io
RBAC: access denied
Istio RABC默认使用拒绝策略,意味着除非显式声明对某个用户的访问权限,其它访问都不被允许。要重新打开customer服务端点的访问,要创建ServiceRole和ServiceRoleBinding声明:
代码语言:javascript复制apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRole
metadata:
name: service-viewer
namespace: tutorial
spec:
rules:
- services: ["*"]
methods: ["GET"]
constraints:
- key: "destination.labels[app]"
values: ["customer", "recommendation", "preference"]
---
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRoleBinding
metadata:
name: bind-service-viewer
namespace: tutorial
spec:
subjects:
- user: "*"
roleRef:
kind: ServiceRole
name: "service-viewer"
应用它:
代码语言:javascript复制 oc -n tutorial apply -f istiofiles/namespace-rbac-policy.yml
再尝试curl命令:
代码语言:javascript复制 curl customer-tutorial.$(minishift ip).nip.io
customer => preference => recommendation v1 from '66b7c9779c': 36
Istio的ServiceRole对象允许你通过名字或使用destionation.labels[app]字段来指定被保护的目标服务。你还可以在methods字段中指定允许的操作,比如GET或POST。ServiceRoleBinding对象允许你指定哪些用户(由其spec. subjects. user字段指定)被允许以什么方式(由ServiceRole的methods字段指定)访问什么服务(由ServiceRole的services字段指定)。上面的示例中,user字段的值为“*”,这意味着任何用户都可以访问这些服务。
请注意,Istio RBAC在Istio 1.4及以后的版本中就不再推荐使用了,而且会在1.6版本中被移除,取而代之的是1.4版本中新增的Authorization Policy。更新信息,请访问Istio Security网站(https://istio.io/docs/concepts/security/)。
7.3 访问策略 - 通过Mixer Policy进行访问控制
Istio的Mixer Policy服务允许你创建ACL规则去保证应用中的各微服务都遵循一个被允许的调用路径。在示例程序中,只允许customer服务调用preference服务,preference服务调用recommendation服务。其它路径都需要被阻止:
- Customer服务不允许调用recommendation服务
- Preference服务不允许调用customer服务
- Recommendation服务不允许调用customer服务
- Recommendation服务不允许调用preference服务
Istio为这种访问控制提供了几个对象或类型,包括denier、checknothing和rule。在使用denier和rule之前,我们来看一下这三个服务之间无序调用带来的风险,这往往容易被忽视。
首先,获得recommendation服务pod的名称和id,然后进入业务容器:
代码语言:javascript复制 RECPOD=$(oc get pod -n tutorial -l app=recommendation -o
'jsonpath={.items[0].metadata.name}')
oc exec -it $RECPOD -n tutorial -c recommendation /bin/bash
然后,通过curl访问customer服务,这能成功:
代码语言:javascript复制 curl customer:8080
customer => preference => recommendation v2 from '7cbd9f9c79': 23
再通过curl访问preference服务:
代码语言:javascript复制 curl preference:8080
preference => recommendation v1 from '66b7c9779c': 152
实际上,业务模式已经确定了只有customer->preference->recommendation这条调用路径是正确的。你可以使用denier和rule来把其它路径禁止掉。这些规则有些长,在应用之前要记得查看它们的声明。
Rule语法的很直接,基于源(source)和目标(destination)。查看pod标签:
代码语言:javascript复制 oc get pods -n tutorial --show-labels
输出如下:
代码语言:javascript复制 NAME READY STATUS LABELS
customer-6564ff969f 2/2 Running app=customer,version=v1
preference-v1-5485dc6f49 2/2 Running app=preference, version=v1
recommendation-v1-66b7c9779c 2/2 Running app=recommendation,version=v1
recommendation-v2-7cbd9f9c79 2/2 Running app=recommendation,version=v2
应用这些规则:
代码语言:javascript复制oc -n tutorial apply -f istiofiles/acl-deny-except-customer2preference2recommendation.yml
现在,再进入recommendation服务,用curl命令访问preference服务:
代码语言:javascript复制 curl preference:8080
PERMISSION_DENIED:do-not-pass-go.denier.tutorial:
Customer -> Preference -> Recommendation ONLY
检查正常调用路径没受到影响:
代码语言:javascript复制 curl customer-tutorial.$(minishift ip).nip.io
customer => preference => recommendation v2 from '7cbd9f9c79': 238
使用oc或kubectl命令查看rules:
代码语言:javascript复制 oc get rules
NAME AGE
no-customer-to-recommendation 3m
no-preference-to-customer 3m
no-recommendation-to-customer 3m
no-recommendation-to-preference 3m
oc describe rule no-preference-to-customer
...
Spec:
Actions:
Handler: do-not-pass-go.denier
Instances:
just-stop.checknothing
Match: source.labels["app"]=="preference" &&
destination.labels["app"] == "customer"
Events: <none>
你可以删除这些规则以恢复之前的状态:
代码语言:javascript复制 oc delete rules --all -n tutorial
Istio Mixer还支持whitelist(白名单)和blacklist(黑名单)机制,通过listchecker和listentry对象。如果你感兴趣,可查阅Istio Tutorial文档(https://github.com/redhat-developer-demos/istio-tutorial)或Istio文档(https://istio.io/docs/)。
请注意,Mixer Policy在Istio 1.5及以后的版本中就不再推荐使用了。
7.4 结论
前面我们快速学习了关于Istio服务网格安全方面的一些知识。你看到了Istio是如何解决在云原生环境中的分布式系统的问题的,还有关于Istio可观测性、弹性、混沌注入等方面的知识。这些都可以被立即应用到你的应用中。
而且,Istio还有很多本书未介绍的功能。如果感兴趣,我们建议你深入学习以下方面的知识:
- Policy enforcement(策略增强)
- Mesh expansion(网格扩展)
- Hybrid deployments(混合部署)
- Phasing in Instio in an existing environment(在已有系统中使用Istio)
- Gateway/Advanced ingress(网关和高级入口)
Istio一直处于快速更新中。要跟进最新进展,建议你关注Istio官网(https://istio.io/)和Red Hat会一直更新的Istio Tutorial网站(https://github.com/redhat-developer-demos/istio-tutorial)。
书籍英文版下载链接为 https://developers.redhat.com/books/introducing-istio-service-mesh-microservices/,作者 Burr Sutter 和 Christian Posta。
本中文译稿版权由本人所有。水平有限,错误肯定是有的,还请海涵。