Istio详细使用-安全(二)
上篇介绍了流量分发管理、熔断、发布等使用,本章主要介绍了Istio的安全认证以及授权策略。
Istio安全的三大目标:
- 默认安全(Security by default):应用程序代码和基础结构无需更改。
- 深度防御(Defense in depth):与现有安全系统集成,提供多层防御。
- 零信任网络(Zero-trust network):在不受信的网络上,构建安全解决方案。
1. 上层架构
Istio的安全性涉及多个部分:
- 用于密钥和证书管理的证书颁发机构(CA)
- 配置API服务器分发给代理
- 认证策略
- 鉴权策略
- 安全命名信息
- 作为策略执行点的僚机和参数代理,用于客户端和服务端之间的安全通信
- 一组用于管理遥测和审计的代理扩展
2. Istio 身份
身份是任何安全基础架构的基本概念。在工作负载间通信开始时, 双方必须交换包含身份信息的凭证以进行双向验证。在客户端, 根据安全命名信息检查服务器的标识, 以查看它是否是工作负载授权的运行程序。在服务器端, 服务器可以根据授权策略确定客户端可以访问哪些信息。
Istio 身份模型使用经典的 service identity
(服务身份)来确定一个请求源端的身份。可以更灵活,细粒度地表示用户、工作负载或一组负载。
3. 身份和凭证管理
Istio使用X.509提供强身份识别。Istio代理,和Envoy一起运行,配合Istiod一起完成自动化大规模的密钥和证书轮换。
Istio 通过以下流程提供密钥和证书:
istiod
提供 gRPC 服务以接受证书签名请求(CSR)。istio-agent
在启动时创建私钥和 CSR,然后将 CSR 及其凭据发送到istiod
进行签名。istiod
CA 验证 CSR 中携带的凭据,成功验证后签署 CSR 以生成证书。- 当工作负载启动时,Envoy 通过 Secret 发现服务(SDS)API 向同容器内的
istio-agent
发送证书和密钥请求。 istio-agent
通过 Envoy SDS API 将从istiod
收到的证书和密钥发送给 Envoy。istio-agent
监控工作负载证书的过期时间。上述过程会定期重复进行证书和密钥轮换。
4. 认证
istio有两种认证类型:
peer authentication
:同行认证用于服务见通信时认证发起连接的客户端。使用双向TLS加密作为全栈解决方案。request authentication
:校验请求中携带的凭证认证终端用户。Istio支持JWT认证,以及一些OIDC供应商的定制话认证。
在所有情况下,Istio 都通过自定义 Kubernetes API 将认证策略存储在Istio config store
。 Istiod 使每个代理保持最新状态, 并在适当时提供密钥。此外,Istio 的认证机制支持宽容模式(permissive mode), 以帮助您在强制实施前了解策略更改将如何影响您的安全状况。
4.1 双向 TLS 认证
Istio 通过客户端和服务器端 PEP 建立服务到服务的通信通道, PEP 被实现为 Envoy 代理。 当一个工作负载使用双向 TLS 认证向另一个工作负载发送请求时, 该请求的处理方式如下:
- Istio 将出站流量从客户端重新路由到客户端的本地 Sidecar Envoy。
- 客户端 Envoy 与服务器端 Envoy 开始双向 TLS 握手。在握手期间, 客户端 Envoy 还做了安全命名检查, 以验证服务器证书中显示的服务帐户是否被授权运行目标服务。
- 客户端 Envoy 和服务器端 Envoy 建立了一个双向的 TLS 连接,Istio 将流量从客户端 Envoy 转发到服务器端 Envoy。
- 服务器端 Envoy 授权请求。如果获得授权,它将流量转发到通过本地 TCP 连接的后端服务
4.1.1 宽容模式
Istio 双向 TLS 具有一个宽容模式(permissive mode), 允许服务同时接受纯文本流量和双向 TLS 流量。这个功能极大地提升了双向 TLS 的入门体验。
在运维人员希望将服务移植到启用了双向 TLS 的 Istio 上时, 许多非 Istio 客户端与非 Istio 服务器端之间的通信会产生问题。 通常情况下,运维人员无法同时为所有客户端安装 Istio Sidecar, 甚至在某些客户端上没有这样做的权限。即使在服务器端上安装了 Istio Sidecar, 运维人员也无法在不中断现有连接的情况下启用双向 TLS。
4.2 认证策略
4.2.1 对等认证
对等认证策略指定 Istio 对目标工作负载实施的双向 TLS 模式。支持以下模式:
PERMISSIVE
:工作负载接受双向 TLS 和纯文本流量。 此模式在迁移因为没有 Sidecar 而无法使用双向 TLS 的工作负载的过程中非常有用。 一旦工作负载完成 Sidecar 注入的迁移,应将模式切换为 STRICT。STRICT
:工作负载仅接收双向 TLS 流量。DISABLE
:禁用双向 TLS。从安全角度来看,除非您提供自己的安全解决方案,否则请勿使用此模式。
如果模式为 unset,将继承父作用域的模式。unset 模式的网格范围的对等认证策略默认使用 PERMISSIVE
模式。
# https://istio.io/latest/zh/docs/reference/config/security/peer_authentication/
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: "example-policy"
namespace: "foo"
spec:
mtls:
mode: STRICT
对于特定于工作负载的对等认证策略,可以为不同的端口指定不同的双向 TLS 模式。 您只能将端口范围的双向 TLS 配置在工作负载声明过的端口上。 以下示例为 app:example-app
工作负载禁用了端口 80 上的双向 TLS, 并对所有其他端口使用命名空间范围的对等认证策略的双向 TLS 设置:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: "example-workload-policy"
namespace: "foo"
spec:
selector:
matchLabels:
app: example-app
portLevelMtls:
80:
mode: DISABLE
4.2.2 请求认证
请求认证策略指定验证 JSON Web Token(JWT)所需的值。这些值包括:
- token 在请求中的位置
- 请求的 issuer
- 公共 JSON Web Key Set(JWKS)
# https://istio.io/latest/zh/docs/reference/config/security/request_authentication/
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "issuer-foo"
jwksUri: https://example.com/.well-known/jwks.json
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
rules:
- from:
- source:
requestPrincipals: ["*"]
5. 授权
Istio 的授权功能为网格中的工作负载提供网格、 命名空间和工作负载级别的访问控制。
5.1 架构
授权策略对服务器端 Envoy 代理的入站流量实施访问控制。 每个 Envoy 代理都运行一个授权引擎,该引擎在运行时授权请求。 当请求到达代理时,授权引擎根据当前授权策略评估请求上下文, 并返回授权结果 ALLOW
或 DENY
。 运维人员使用 .yaml
文件指定 Istio 授权策略。
5.2 策略逻辑
stio 按以下顺序检查层中的匹配策略:CUSTOM
、DENY
, 然后是 ALLOW
。对于每种类型的操作,Istio 首先检查是否有策略的操作已被应用, 然后检查请求是否匹配策略的规则。如果请求与其中一层中的策略不匹配, 则检查将继续到下一层。
5.3 授权策略
要配置授权策略,请创建一个 AuthorizationPolicy
自定义资源。 一个授权策略包括选择器(selector)、动作(action)和一个规则(rules)列表:
selector
字段指定策略的目标action
字段指定允许还是拒绝请求rules
指定何时触发动作rules
下的from
字段指定请求的来源rules
下的to
字段指定请求的操作rules
下的when
字段指定应用规则所需的条件
5.3.1 策略目标
以下示例显示了一个授权策略,该策略允许两个源(服务帐户 cluster.local/ns/default/sa/sleep
和命名空间 dev
),在使用有效的 JWT 令牌发送请求时,可以访问命名空间 foo
中带有标签 app: httpbin
和 version: v1
的工作负载。
# https://istio.io/latest/zh/docs/reference/config/security/authorization-policy/
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
version: v1
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/sleep"]
- source:
namespaces: ["dev"]
to:
- operation:
methods: ["GET"]
when:
- key: request.auth.claims[iss]
values: ["https://accounts.google.com"]
5.3.2 值匹配
授权策略中的大多数字段都支持以下所有匹配模式:
- 完全匹配:即完整的字符串匹配。
- 前缀匹配:
"*"
结尾的字符串。例如,"test.abc.*"
匹配"test.abc.com"
、"test.abc.com.cn"
、"test.abc.org"
等等。 - 后缀匹配:
"*"
开头的字符串。例如,"*.abc.com"
匹配"eng.abc.com"
、"test.eng.abc.com"
等等。 - 存在匹配:
*
用于指定非空的任意内容。您可以使用格式fieldname: ["*"]
指定必须存在的字段。这意味着该字段可以匹配任意内容,但是不能为空。 请注意这与不指定字段不同,后者意味着匹配包括空的任意内容。
有一些例外。例如,以下字段仅支持完全匹配:
when
部分下的key
字段source
部分下的ipBlocks
to
部分下的ports
字段
以下示例策略允许访问前缀为 /test/*
或后缀为 */info
的路径。
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: tester
namespace: default
spec:
selector:
matchLabels:
app: products
action: ALLOW
rules:
- to:
- operation:
paths: ["/test/*", "*/info"]
5.3.3 排除匹配
为了匹配诸如 when
字段中的 notValues
、 source
字段中的 notIpBlocks
、to
字段中的 notPorts
之类的否定条件,Istio 支持排除匹配。
以下示例:如果请求路径不是 /healthz
,则要求从请求的 JWT 认证中导出的主体是有效的。 因此,该策略从 JWT 身份验证中排除对 /healthz
路径的请求:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: disable-jwt-for-healthz
namespace: default
spec:
selector:
matchLabels:
app: products
action: ALLOW
rules:
- to:
- operation:
notPaths: ["/healthz"]
from:
- source:
requestPrincipals: ["*"]
5.3.4 自定义条件
您还可以使用 when
部分指定其他条件。 例如,下面的 AuthorizationPolicy
定义包括以下条件: request.headers [version]
是 v1
或 v2
。 在这种情况下,key 是 request.headers [version]
, 它是 Istio 属性 request.headers
(这是个字典)中的一项。
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
version: v1
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/sleep"]
to:
- operation:
methods: ["GET"]
when:
- key: request.headers[version]
values: ["v1", "v2"]