Istio系列学习(三) istio服务访问管理
1 引言
先思考一个问题:在kubernetes的网络环境下,如何做服务间的访问控制 和 提高访问的安全性?
你可能会想到 进行HTTPS加密、使用NetworkPolicy、使用RBAC控制权限...等等
总结起来要实现以下能力:
数据加密:确保传输中的数据经过加密,难以被截获破解
身份认证:确定客户端和服务端都是真实且经过验证的
权限控制:默认拒绝掉来历不明的请求,开放最小粒度的访问权限给必要的访问者
而原生kubernetes访问控制能力存在着缺陷:
没有对应用层协议的分析处理能力。
ServiceAccount 可以做到把服务和身份关联(每个deployment创建一个ServiceAccount),但一个sa token 只能在一个集群中标记负载身份,不能支持 Kubernetes 环境和虚拟机的异构场景、多kubernetes集群联合使用场景。
对此问题istio提出了解决方案:
为每个pod的sidecar自动下发x509证书,支持pod间使用mtls加密通信。
使用了SPIFFE (Secure Production Identity Framework For Everyone)开源的身份认证标准,标识工作负载(身份)。SPIFFE ID 是一个统一资源标识符, istio中 SPIFFE ID 格式为:spiffe://<trust_domain>/ns/<namespace>/sa/<service_ account> 。在kubernetes平台上,istio巧妙的借助kubernetes工作负载的Service Account信息,在istio生成工作负载证书时将 SPIFFE ID 字符串注入 X509 证书的 subjectAltName扩展中,随着 X.509 证书下发到数据面。通信双方通过这个字符串即可获得对方身份,实现了istio中的身份标识。(若工作负载没有配置服务账户的情况下使用命名空间的默认ServiceAccount)。其中“trust domain”参数通过 Istiod 环境变量 TRUST_DOMAIN 注入,用于在多kubernetes集群环境。
通过sidecar代理 拦截处理访问流量,进行权限管控。
2 istio访问管理架构
Istio 为微服务提供了无侵入,可插拔的底层安全基础设施。在istio中服务间端到端的通信安全通过服务网格中的代理实现,在整个请求过程中客户端和服务端应用程序是不感知的。
本文将从istio 证书管理、身份认证、访问鉴权策略 三个方面来介绍 Istio 的安全机制。
istio访问管理架构
3 istio证书管理
Istio通过向数据面代理颁发X.509证书来为工作负载提供身份标识。
证书管理和轮换由与Envoy代理在同一容器中运行的pilot-agent 进程代理完成。
Istio 通过以下流程管理证书:
istio证书下发管理流程
istiod 提供 gRPC 服务以接受证书签名请求(CSR)。
pilot-agent 在启动时创建私钥和 CSR,然后将 CSR 及其凭据发送到 istiod 进行签名。
istiod CA 验证 CSR 中携带的凭据,成功验证后签署 CSR 以生成证书,证书中包含对应的SPIFFEID。
当工作负载启动时,Envoy 通过 Secret 发现服务(SDS)API 向同容器内的 pilot-agent 发送证书和密钥请求。
pilot-agent 通过 Envoy SDS API 将从 istiod 收到的证书和密钥发送给 Envoy。
pilot-agent 监控工作负载证书的过期时间。上述过程会定期重复进行证书和密钥轮换。
4 istio身份认证
认证(Authentication)一般是指验证当前用户的身份,对于微服务而言则是验证请求对端的身份。在安全的理论模型中,身份是任何安全功能的基础条件。
Istio 提供两种类型的身份认证:
对等认证:基于mtls来实现的服务到服务的认证。(即通过x509证书获取对端身份),一般用于kubernetes集群内的服务之间的认证。
请求认证:基于jwt(JSON Web Token)来实现的请求级别的认证,它常被用于终端用户身份认证。
当前istio技术落地时大多数使用对等认证。istio请求认证的使用较少,大多数情况下jwt解析是在应用程序中进行,而不是istio代理解析,因为应用程序需要获取终端用户id来对数据库操作。
本文不介绍请求认证。
4.1 对等认证的概念和原理
TLS 协议是TCP/IP协议组的一部分,用于在两台主机间建立一个加密的双向网络通道。TLS协议一般会和其他协议结合使用来提供安全的应用层,例如我们熟知的HTTPS、FTPS等。
mTLS是TLS协议的扩展,旨在验证双方(Web客户端和Web服务器)的身份并保护他们在网络中的通信安全。
TLS单向认证与mTLS双向认证的区别如下:
TLS单向认证:只有客户端验证服务端的合法身份,服务端不关心客户端身份。服务端维护证书。web应用大多数是单向的TLS认证。
mTLS双向认证:客户端和服务端都要验证对端身份。客户端和服务端都必须持有标识身份的证书。常用于服务到服务的访问。
Istio对等认证基于mTLS,实现以下能力:
真实性:确保双方都是真实且经过验证的
保密性:确保传输中的数据安全
完整性:确保发送数据的正确性
4.2 对等认证的架构
istio的对等认证(mtls)是由网格代理完成的,在整个请求过程中客户端和服务端应用程序是不感知的。
mTLS认证架构和认证过程
4.3 对等认证生效范围
PeerAuthentication 的 metadata.namespace 和 spec.selector 字段 决定对等认证生效范围。
全网格范围:metadata.namespace 为istio-system 且 selector 字段为空。
命名空间范围:metadata.namespace 非istio-system ,selector 字段为空。
特定工作负载:metadata.namespace 非istio-system , selector 字段非空。
只能有一个网格范围的对等认证策略, 每个命名空间也只能有一个命名空间范围的对等认证策略。
一个工作负载只能生效一个认证规则,Istio 按照以下顺序为每个工作负载应用匹配的策略:
特定工作负载的规则 > 命名空间范围规则 > 全网格范围规则
4.4 对等认证生效位置
认证策略是对服务收到的请求生效的,即服务端生效。
4.5 对等认证配置
对等认证示例如下:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: "httpbin"
namespace: "bar" ## 只可生效于"bar"命名空间下的工作负载
spec:
selector: ## 通过label匹配pod
matchLabels:
app: httpbin # 匹配 httpbin 应用的标签
mtls:
mode: STRICT ## 安全模式,STRICT代表只接受mtls请求
其中spec.mtls.mode有3种可选值,对应3种身份认证模式:
PERMISSIVE:同时支持明文传输和mtls密文传输。不管是网格内还是网格外,服务间都可通信。
STRICT:此服务只接收双向 TLS 流量。
DISABLE:关闭MutualTLS。从安全的角度而言,官方并不建议在没有其他安全措施的情况下使用该模式。
缺省配置时:将从父级配置中继承(命名空间或网格层面),如果父级没有进行相应的配置,则使用PERMISSIVE模式。
4.4 对等认证部署验证
我们先准备下面的sleep.yaml 和 httpbin.yaml
## sleep.yaml文件
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
---
apiVersion: v1
kind: Service
metadata:
name: sleep
labels:
app: sleep
service: sleep
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
terminationGracePeriodSeconds: 0
serviceAccountName: sleep
containers:
- name: sleep
image: curlimages/curl
command: ["/bin/sleep", "infinity"]
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /etc/sleep/tls
name: secret-volume
volumes:
- name: secret-volume
secret:
secretName: sleep-secret
optional: true
## httpbin.yaml文件
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http-http
port: 80
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kong/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
执行以下操作
## 创建三个命名空间
kubectl create ns foo
kubectl create ns bar
kubectl create ns legacy
## 对其中两个命名空间开启istio自动注入,另一个不开启istio自动注入
kubectl label namespace foo istio-injection=enabled
kubectl label namespace bar istio-injection=enabled
## 应用上面的yaml文件
kubectl apply -f httpbin.yaml -n foo
kubectl apply -f sleep.yaml -n foo
kubectl apply -f httpbin.yaml -n bar
kubectl apply -f sleep.yaml -n bar
kubectl apply -f sleep.yaml -n legacy
实验先从默认的PERMISSIVE模式下开始,PERMISSIVE模式下服务网格对服务的流量都是放通的。遍历不同空间的sleep工作负载访问httpbin工作负载,可以看到是全通的。
服务间访问测试
生效下面的对等认证规则
## pa.yaml文件
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: "example-peer-policy"
namespace: "foo"
spec:
selector:
matchLabels:
app: reviews
mtls:
mode: STRICT
执行如下命令进行测试:
for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:80/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
应用PeerAuthentication严格模式后,istio代理会自动把请求流量升级为mtls模式,所以foo、bar命名空间的请求正常。而legacy命名空间没有注入sidecar,没有代理为其实现mtls,请求失败。
服务间访问测试
4.5 使用需知
当服务开启严格模式的对等认证,只能用svc来访问pod,无法用podip去请求此工作负载。
bug详情连接 https://github.com/istio/istio/issues/37431#issuecomment-1054450785
5 istio服务访问授权
5.1 访问授权概念和原理
在安装完istio后,默认没有服务访问策略,这时允许全部的服务间访问。
istio提供了AUthorizationPolicy CRD来设置服务访问授权。例如:哪个服务、身份、源IP 可以访问某个服务的某个端口、接口。
由于istio作用的对象是服务,因此授权功能主要适用于四至七层(相比较而言,传统的防火主用于二至四层),例如,gRPC、HTTP、HTTPS、HTTP2,以及TCP等。上述协议Istio都可以提供原生支持。
就像istio的其他流量控制功能一样,Istio中授权功能的实现也是非侵入式的,通过sidecar代理完成。
5.2 访问授权架构
服务网格数据面是真正的授权策略执行角色。规则的配置、下发、执行架构如下:
运维人员将AuthorizationPolicy应用到k8s中,存储在etcd
istiod从 k8s中获取授权配置策略,下发到数据面
envoy在拦截到请求后根据授权策略判断是否允许访问。
istio访问授权架构
5.3 访问授权配置
AuthorizationPolicy 包括三个主要字段 selector、action、rules
selector 字段指定策略的目标
rules 指定何时触发动作
rules 下的 from 字段描述请求来源属性。可以不设置,表示匹配所有来源的请求。 from数组元素间是或的关系,form元素内部各个条件是与的关系。
rules 下的 to 字段述对目标服务的操作属性,如:port methods urlpath。若不配置to条件,则表示匹配所有操作。
rules 下的 when 字段指定应用规则所需的条件
action 字段指定最后的操作,支持 ALLOW、DENY 和 CUSTOM
以下示例授权策略拒绝来源非 foo 命名空间的对 httpbin的 "GET" 请求。我们生效配置,然后再次执行循环请求脚本。
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
selector: ## 通过标签指定生效的服务
matchLabels:
app: httpbin
version: v1
action: DENY ## 拒绝请求
rules: ## 匹配请求的条件
- from: ## 不是来自foo命名空间的请求
- source:
notNamespaces: ["foo"]
to: ## Get方法的请求
- operation:
methods: ["GET"]
测试结果如下图:
访问授权测试
5.4 访问授权生效顺序
拒绝策略优先于允许策略。如果请求同时匹配上允许策略和拒绝策略,请求将被拒绝。
访问授权判断顺序
5.5 "访问授权"对"身份认证"的依赖
Istio对等认证使用双向 TLS 将某些信息从客户端安全地传递到服务器,这让服务端可以知道请求者的真实身份。 在使用授权策略中的以下任何字段之前,必须先启用双向 TLS:
source 部分下的 principals 和 notPrincipals 字段
source 部分下的 namespaces 和 notNamespaces 字段
source.principal 自定义条件
source.namespace 自定义条件
6 实战经验
6.1 使用访问管控时,建议开启envoy代理日志,便于分析异常问题
kubectl -n istio-system edit configmap istio
编辑yaml文件的对应配置:
data:
mesh: |-
accessLogEncoding: JSON
accessLogFile: /dev/stdout
6.2 更新认证策略最佳实践
当更改认证策略时,Istio 无法保证所有工作负载都同时收到新政策。 以下建议有助于避免在更新认证策略时造成干扰:
将对等认证策略的模式从 DISABLE 更改为 STRICT 时, 请使用 PERMISSIVE 模式来过渡,反之亦然。当所有工作负载成功切换到所需模式时, 您可以将策略应用于最终模式。您可以使用 Istio 遥测技术来验证工作负载已成功切换。
将请求认证策略从一个 JWT 迁移到另一个 JWT 时, 将新 JWT 的规则添加到该策略中,而不删除旧规则。这样, 工作负载将接受两种类型的 JWT,当所有流量都切换到新的 JWT 时, 您可以删除旧规则。但是,每个 JWT 必须使用不同的位置。
6.3 禁止访问公网
有时候为了安全我们不允许kubernetes pod 随意访问公网,可以设置REGISTRY_ONLY。
在传统环境这个需求是由出口防火墙实现,但是在云环境上一般使用snat对整个子网进行出访代理,可以在istio中进行限制。
## 修改访问模式为REGISTRY_ONLY
kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -
## 切换回Allow-all
kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: REGISTRY_ONLY/mode: ALLOW_ANY/g' | kubectl replace -n istio-system -f
在完成了修改之后,所有访问外部服务的请求都会返回 502 错误。为了正常访问,需要对外部服务进行注册,注册可通过创建服务条目资源来完成。下面的代码创建了第三方支付服务所对应的 ServiceEntry 资源。
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: baidu-ip
spec:
hosts:
- www.baidu.com
addresses:
- 36.152.44.96
location: MESH_EXTERNAL ## 声明了该服务位于服务网格之外
ports:
- number: 80 ## 定义了允许访问的端口
name: baidu-http
protocol: HTTP
resolution: NONE ## 属性表示服务代理如何解析服务的 IP 地址
6.4 istio入口网关黑名单
您使用 X-Forwarded-For HTTP 标头或代理协议来确定原始客户端 IP 地址,那么可以在 AuthorizationPolicy 中使用 remoteIpBlocks 设置黑白名单。
下面yaml配置禁止来自 "1.2.3.4" 和 “5.6.7.0/24” 网段的请求进入istio
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: DENY
rules:
- from:
- source:
remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24"]
作者:BeijingToTokyo https://www.bilibili.com/read/cv29652221/ 出处:bilibili
























