本文转载自 伪架构师。
Istio 为网格中的微服务提供了较为完善的安全加固功能,在不影响代码的前提下,可以从多个角度提供安全支撑,官方文档做了较为详细的介绍,但是也比较破碎,这里尝试做个简介兼索引,实现过程还是要根据官方文档进行。
Istio 的安全功能主要分为三个部分的实现:
- 双向 TLS 支持。
- 基于黑白名单的访问控制。
- 基于角色的访问控制。
- JWT 认证支持。
首先回顾一下 Istio 网格中的服务通信过程:
- 利用自动或者手工注入,把 Envoy Proxy 注入到每个服务 Pod 中,用 Sidecar 的方式运行。
- Pod 初始化过程里,使用 iptables 劫持所在 Pod 的出入流量。
- 服务间的通信,从原来的直接通信,转换为现在的 Envoy 之间通信,Envoy 在这里同时作为客户端和服务端负载均衡组件。
- Envoy 的工作过程中,可能会和 Mixer、Pilot 以及 Citadel 等组件发生互动。
双向 TLS 支持
双向 TLS 支持主要针对的是通信方面,把明文传输的服务通信,通过转换为 Envoy 之间的加密通信。这一安全设置较为基础,可以在全局、Namespace 或者单个服务的范围内生效。
这一功能主要通过两个 Istio CRD 对象来完成:
Policy
例如 Basic Authentication Policy 中的一个样例,用于给单个服务设置 mtls:
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "example-2"
spec:
targets:
- name: httpbin
peers:
- mtls:
其中 target
是可选项,如果去掉的话,作用域将扩展到整个 Namespace。
DestinationRule
同样的一个例子里面的目标规则如下:
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "example-2"
spec:
host: httpbin.bar.svc.cluster.local
trafficPolicy:
tls:
mode: DISABLE
portLevelSettings:
- port:
number: 1234
tls:
mode: ISTIO_MUTUAL
这个也很容易理解,这一规则用于指派对该地址的访问方式:
tls.mode = DISABLE
,这个服务缺省是不开启 tls 支持的,如果取值ISTIO_MUTUAL
,则代表这个地址(服务)的所有端口都开启 TLS。port...ISTIO_MUTUAL
,只针对这一个端口启用 mTLS 支持。
创建 Policy 之后,Citadel 会生成证书文件,并传递给 Envoy,我们可以在 Envoy 容器(kube-proxy)的 /etc/certs/
目录中看到这几个 *.pem
文件。如果使用 openssl x509 -text -noout
查看 cert-chain.pem
的证书内容,会看到 spiffe 编码的 ServiceAccount 内容来作为 SAN:
X509v3 Subject Alternative Name:
URI:spiffe://cluster.local/ns/default/sa/default
规则生效之后,原有的服务间调用是没有差异的,但是如果在网格之外,就必须 https,结合上面谈到的证书来访问目标服务才能完成访问。
另外这里也提供了外部 CA 的支持,可以使用已有的证书体系来替换网格内的自签发体系。
基于黑白名单的访问控制
黑名单
下面的例子来自官方,禁止 Reviews 的 v3 版本访问 Ratings 服务。
首先使用 denier
适配器定义一个拒绝响应
apiVersion: "config.istio.io/v1alpha2"
kind: denier
metadata:
name: denyreviewsv3handler
spec:
status:
code: 7
message: Not allowed
这里不需要额外属性输入,因此采用了 checknothing
模板:
apiVersion: "config.istio.io/v1alpha2"
kind: checknothing
metadata:
name: denyreviewsv3request
spec:
最后使用 rule
对象把这两者联系起来,并配合一个表达式来使之生效:
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
name: denyreviewsv3
spec:
match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
actions:
- handler: denyreviewsv3handler.denier
instances: [ denyreviewsv3request.checknothing ]
白名单
官方案例设置了一个允许 v2
和 v3
版本访问 ratings
服务的白名单。
白名单适配器要使用的是 listchecker
,提供了一个允许访问的数组。
apiVersion: config.istio.io/v1alpha2
kind: listchecker
metadata:
name: whitelist
spec:
# providerUrl: 可以从外部 URL 获取列表内容
overrides: ["v1", "v2"] # 静态列表
blacklist: false
需要使用一个模板将 Pod 标签转换为 listchecker
的版本列表。
apiVersion: config.istio.io/v1alpha2
kind: listentry
metadata:
name: appversion
spec:
value: source.labels["version"]
最后使用 Rule 进行连接:
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: checkversion
spec:
match: destination.labels["app"] == "ratings"
actions:
- handler: whitelist.listchecker
instances:
- appversion.listentry
注意:如果开启了 mTLS,可以使用
source.user == "cluster.local/ns/default/sa/bookinfo-productpage"
的形式来匹配 ServiceAccount。
RBAC
Helm 安装时,需要设置
global.rbacEnabled: true
。
RBAC 提供较细粒度的访问控制。另外其中所使用的 ServiceRole
和 ServiceRoleBinding
也更直观、更加易于管理。
例如来自官方 Task 的 ServiceRole
定义,这个角色允许对指定服务进行只读访问:
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata:
name: productpage-viewer
namespace: default
spec:
rules:
- services: ["productpage.default.svc.cluster.local"]
methods: ["GET"]
如果在 Namespace 级别进行设置,则可以这样:
...
rules:
- services: ["*"]
methods: ["GET"]
constraints:
- key: "app"
values: ["productpage"]
...
和 Kubernetes 的 Rolebinding 类似,把用户和角色绑定起来,才能最后生效。
例如:
- user: alice@yahoo.com
或者
- properties:
service: "reviews"
namespace: "abc"
subject
的内容,同样属于 Adapter 模型的实现范围,因此其可选项目仍然是由 Template 的输入产生的。具体样例可以参考 bookinfo 的 rbac 样板
JWT 认证
没有外部认证的需求,因此就先不理了 lol。
参考链接
- 安全任务:https://istio.io/docs/tasks/security
- Istio RBAC 参考:https://istio.io/docs/reference/config/istio.rbac.v1alpha1/
- Istio Adapters 参考:https://istio.io/docs/reference/config/policy-and-telemetry/adapters/
- Bookinfo 示例:https://github.com/istio/istio/blob/release-0.8/samples/bookinfo/kube/