一、前言¶
在Kubernetes集群中,可以对用户进行RBAC授权role,rolebinding,clusterrole, clusterrolebinding;
通过前面章节,咱们在 Istio 中,有多个组件参与提供安全功能:
- 用于管理钥匙和证书的证书颁发机构(CA);
- Sidecar 和周边代理:实现客户端和服务器之间的安全通信;
- Envoy 代理扩展:管理遥测和审计;
对于 istio来说,可以对资源定义 AuthorizationPolicy 授权策略,执行拒绝、允许或审计动作。
二、Istio授权¶
2.1 什么是授权?¶
Istio 的授权是指对服务之间进行访问控制和权限管理的能力。通过 Istio 的授权功能, 咱们可以定义规则来限制哪些服务可以访问其他服务,并定义请求的特定条件和操作。
例如:某个(经过认证的)主体是否被允许操作某个对象?用户 A 能否向服务 B 的路径 /hello 发送一个 GET 请求?
大家要注意,尽管主体可以被认证,但它可能不被允许执行某个动作。你的公司 ID 卡可 能是有效的、真实的,但我不能用它来进入另一家公司的办公室。再另外一个比喻,我 们可以说授权类似于你护照上的签证章。
这就引出了下一个问题 —— 有认证而无授权(反之亦然)对我们没有什么好处。对于适 当的访问控制,我们需要两者。我来 举个例子:如果我们只认证主体而不授权他们,他 们就可以做任何他们想做的事,对任何对象执行任何操作。相反,如果我们授权了一个 请求,但我们没有认证它,我们就可以假装成其他人,再次对任何对象执行任何操作。
Istio 允许我们使用 AuthorizationPolicy AuthorizationPolicy 支持 DENY、 ALLOW、 AUDIT 和 CUSTOM 操作。
每个 Envoy 代理实例都运行一个授权引擎,在运行时对请求进行授权。当请求到达代理 时,引擎会根据授权策略评估请求的上下文,并返回 ALLOW 或 DENY。AUDIT 动作决定是否记录符合规则的请求。注意,AUDIT 策略并不影响请求被允许或拒绝。
如果没有必要明确地启用授权功能。为了执行访问控制,我们可以创建一个授权策略来 应用于我们的工作负载。
AuthorizationPolicy 资源是我们可以利用 PeerAuthentication 策略和 RequestAuthentication 策略中的主体的地方。
在定义 AuthorizationPolicy (授权策略)的时候,需要考虑以下三个关键部分 :
- 选择目标:明确哪些服务或工作负载需要进行访问控制。可以通过标签选择器、命 名空间或具体的服务名称来指定。
- 定义动作:确定要对选定的目标执行的操作。包括允许(ALLOW)、拒绝 (DENY)或中止(ABORT)请求。
- 规定规则:定义一组规则来决定请求是否满足授权条件。这些规则可以基于请求的属性(如源 IP、用户或标头)以及特定的 HTTP 方法、路径等进行匹配。
让我们看看下面这个例子如何与 AuthorizationPolicy 资源中的字段相对应:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: customers-deny
namespace: default
spec:
selector:
matchLabels:
app: customers
version: v2
action: DENY
rules:
- from:
- source:
notNamespaces: ["default"]
使用 selector 和 matchLabel s ,我们可以选择策略所适用的工作负载 。在我们的案例中,我们选择的是所有设置了app: customers 和 version: v2 标签的工作负载。 action 字段被设置为 DENY。
最后,我们在 rules 字段中定义所有规则。这个例子中的规则是说,当请求来自default 命名空间之外时,拒绝对customers v2 工作负载的请求(action)。
除了规则中的 from 字段外,我们还可以使用 to 和 when 字段进一步定制规则 。让我们看一个使用这些字段的例子:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: customers-deny
namespace: default
spec:
selector:
matchLabels:
app: customers
version: v2
action: DENY
rules:
- from:
- source:
notNamespaces: ["default"]
to:
- operation:
methods: ["GET"]
when:
- key: request.headers[User-Agent]
values: ["Mozilla/*"]
我们在规则部分添加了 to 和 when 字段。我来带着大家看一下这个yaml的字面意思,当客户的 GET 请求来自 default 命名空间之外,并且 User Agent 头的值与正则表达式 Mozilla/\ 相匹配时,我们会拒绝customers v2 的工作负载*。
总的来说, to 定义了策略所允许的行动, from 定义了谁可以采取这些行动, when 定义了每个请求必须具备的属性, selector 定义了哪些工作负载将执行该策略。
当一个工作负载有多个 Istio 的 AuthorizationPolicy (授权策略)时,授权策略支持allow 和 deny 策略,当 allow 和 deny 同时作用于工作负载时,deny 优先。下面是具体的规则:
- 如果请求匹配任何 DENY 策略,拒绝请求。
- 如果工作负载没有任何的 ALLOW 策略,放行请求。
- 如果请求匹配任何 ALLOW 策略,放行请求。
- 拒绝请求。
2.2 来源¶
我们在上述例子中 使用的源是 not Names paces 。我们还可以使用以下任何一个字段来 指定请求的来源,如表中所示。
| 来源 | 示例 | 释义 |
|---|---|---|
| principals | principals: ["my- service-account"] | 任何是有my-service-account的工作负载 |
| notPrincipals | notPrincipals: ["my- service-account"] | 除了my-service-account的任何工作负载 |
| requestPrincipals | requestPrincipals: ["my-issuer/hello"] | 任何具有有效 JWT和请求主体my-issuer/hello的工作负载 |
| otRequestPrincipals | notRequestPrincipals: ["*"] | 任何没有请求主体的 工作负载(只有有效 的 JWT 令牌)。 |
| namespaces | namespaces: ["default"] | 任何来自 default 命名空间的工作负载 |
| notNamespaces | notNamespaces: ["prod"] | 任何不在 prod 命 名空间的工作负载 |
| ipBlocks | ipBlocks:["1.2.3.4","9.8.7.6/15"] | 任何具有 1.2.3.4 的 IP 地址或来自9.8.7.6/15 地址段的工作负载 |
| notIpBlock | notIpBlocks:["1.2.3.4/24"] | 任何不在1.2.3.4/24 地址段的工作负载 |
2.3 操作¶
操作被定义在 to 字段下,如果多于一个,则使用 AND 语义。 就像来源 [from]一样,操 作是成对的,有正反两面的匹配。设置在操作字段的值是字符串:
- hosts 和 notHosts
- ports 和 notPorts
- methods 和 notMethods
- paths 和 notPath
所有这些操作都适用于请求属性。
例如,要在一个特定的请求路径上进行匹配:
使用路径: paths:["/api/\*","/admin"]
特定的端口: ports: ["8080"]
以此类推...
### 10.2.4 条件
为了指定条件,我们必须提供一个 key 字段。 key 字段是一个 Istio 属性的名称。 例如, request.headers、 source.ip、 destination.port 等等。关于支持的属性的完整列表,请参考 授权政策条件。
条件的第二部分是 values 或 notValues 的字符串列表。下面是一个 when 条件的片段:
...
- when:
- key: source.ip
notValues: ["10.0.1.1"]
三、实战:授权(访问控制)(1)¶
3.1 访问控制¶
在这个实验中,我们将学习如何使用授权策略来控制工作负载之间的访问。 例如,如下这个需求:
- 针对 default 命名空间进行访问控制;
- 仅允许特定 namespace 下的 SA 应用进行访问;
首先部署 Gateway:
[root@master01 istioyaml]# vim gateway-6.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
将上述 YAML 保存为 gateway-6.yaml,并使用 kubectl apply -f gateway-6.yaml部署网关。
[root@master01 istioyaml]# kubectl apply -f gateway-6.yaml
接下来,我们将创建 Web 前端部署、服务账户、服务 和 VirtualService。
[root@master01 istioyaml]# vim web-frontend-6.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
replicas: 1
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
version: v1
spec:
serviceAccountName: web-frontend
containers:
- image: registry.cn-hangzhou.aliyuncs.com/github_images1024/web-frontend:1.0.0
imagePullPolicy: Always
name: web
ports:
- containerPort: 8080
env:
- name: CUSTOMER_SERVICE_URL
value: 'http://customers.default.svc.cluster.local'
---
kind: Service
apiVersion: v1
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
selector:
app: web-frontend
ports:
- port: 80
name: http
targetPort: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: web-frontend
spec:
hosts:
- '*'
gateways:
- gateway
http:
- route:
- destination:
host: web-frontend.default.svc.cluster.local
port:
number: 80
将上述 YAML 保存为 web-frontend-6.yaml ,并使用 kubectl apply -f web-frontend-6.yaml创建资源。
[root@master01 istioyaml]# kubectl apply -f web-frontend-6.yaml
最后,我们将部署 customers v1 服务。
[root@master01 istioyaml]# vim customers-v4.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: customers-v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: customers-v1
labels:
app: customers
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: customers
version: v1
template:
metadata:
labels:
app: customers
version: v1
spec:
serviceAccountName: customers-v1
containers:
- image: registry.cn-hangzhou.aliyuncs.com/github_images1024/customers:1.0.0
imagePullPolicy: Always
name: svc
ports:
- containerPort: 3000
---
kind: Service
apiVersion: v1
metadata:
name: customers
labels:
app: customers
spec:
selector:
app: customers
ports:
- port: 80
name: http
targetPort: 3000
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
将上述内容保存为 customers-v4.yaml ,并使用 kubectl apply -f customers-v4.yaml 创建部署和服务。
[root@master01 istioyaml]# kubectl apply -f customers-v4.yaml
如果我们打开 GATEWAY_URL ,应该会显示带有 customers v1 服务数据的 web 前端页面。
我们先来创建一个授权策略,拒绝 default 命名空间的所有请求。
[root@master01 istioyaml]# vim deny-all.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: default
spec:
{}
将上述内容保存为 deny-all.yaml ,并使用 kubectl apply -f deny-all.yaml创建该策略。
[root@master01 istioyaml]# kubectl apply -f deny-all.yaml
如果我们尝试访问 GATEWAY_URL ,我们将得到以下响应。
[root@master01 istioyaml]# curl http://10.0.0.12/
RBAC: access denied
同样,如果我们试图在集群内运行一个 Pod,并从 default 命名空间内向 Web 前端或customers 服务提出请求,我们会得到同样的错误。
来我们试试看:
[root@master01 istioyaml]# kubectl run curl -it --image=registry.cn-hangzhou.aliyuncs.com/github_images1024/busyboxplus:curl
If you don't see a command prompt, try pressing enter.
[ root@curl:/ ]$ curl customers
curl: (56) Recv failure: Connection reset by peer
[ root@curl:/ ]$ curl web-frontend
curl: (56) Recv failure: Connection reset by peer
在这两种情况下,我们都得到了拒绝访问的错误。
我们要做的第一件事是:使用 ALLOW 动作,允许从入口网关向 web-frontend 应用程序发送请求。在规则中,我们指定了入口网关运行的源命名空间( istio-system )和入口网关的服务账户名称。
[root@master01 istioyaml]# vim allow-ingress-frontend.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-ingress-frontend
namespace: default
spec:
selector:
matchLabels:
app: web-frontend
action: ALLOW
rules:
- from:
- source:
namespaces: ["istio-system"]
- source:
principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
将上述内容保存为 allow-ingress-frontend.yaml ,并使用 kubectl apply -f allow-ingress-frontend.yaml 创建策略。
[root@master01 istioyaml]# kubectl apply -f allow-ingress-frontend.yaml
如上 yaml 的主要意思是:这个授权策略允许拥有 "app: web-frontend" 标签的工作负载接收来自 "istio-system" 命名空间和指定的服务账号身份的请求。其他来源的请求将被拒绝访问该工作负载。
如果我们尝试从我们的主机向 GATEWAY_URL 发出请求,这次我们会得到一个不同的错误。
[root@master01 istioyaml]# curl http://10.0.0.12/
"Request failed with status code 403"
请注意,策略需要几秒钟才能分发到所有代理,所以你可能仍然会看到 RBAC:access denied 的消息,时间为几秒钟。
这个错误来自 customers 服务——记得我们允许调用 Web 前端。然而, web-frontend 仍然不能调用 customers 服务。
如果我们回到我们在集群内运行的 curl Pod,尝试请求 http://web-frontend ,我们会得到一个 RBAC 错误。 DENY 策略是有效的,我们只允许从入口网关进行调用。
[root@master01 istioyaml]# kubectl exec curl -- curl http://web-frontend
RBAC: access denied
当我们部署 Web 前端时,我们也为 Pod 创建了一个服务账户(否则,命名空间中的所 有 Pod 都被分配了默认的服务账户)。现在我们可以使用该服务账户来指定 customers 服务调用的来源。
[root@master01 istioyaml]# vim allow-web-frontend-customers.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-web-frontend-customers
namespace: default
spec:
selector:
matchLabels:
app: customers
version: v1
action: ALLOW
rules:
- from:
- source:
namespaces: ["default"]
source:
principals: ["cluster.local/ns/default/sa/web-frontend"]
将上述 YAML 保存为 allow-web-frontend-customers.yaml ,并使用 kubectl apply -f allow-web-frontend-customers.yaml 创建策略。
[root@master01 istioyaml]# kubectl apply -f allow-web-frontend-customers.yaml
一旦策略被创建,我们将看到 Web 前端再次工作——它将获得 customers 服务的回应
[root@master01 istioyaml]# curl http://10.0.0.12/ -I
HTTP/1.1 200 OK
x-powered-by: Express
content-type: text/html; charset=utf-8
content-length: 2471
etag: W/"9a7-hEXE7lJW5CDgD+e2FypGgChcgho"
date: Thu, 17 Apr 2025 12:04:00 GMT
x-envoy-upstream-service-time: 11
server: istio-envoy
如上我们使用了多个授权策略,明确地允许从入口到前端以及从前端到 customers 服务 的调用。
使用 deny-all 策略是一个很好的开始,因为我们可以控制、管理,然后再次针对可以 放行的接口或应用进行权限的放开操作。
3.2 清理¶
删除 Deployment、Service、VirtualService 和 Gateway:
kubectl delete sa customers-v1 web-frontend
kubectl delete deploy web-frontend customers-v1
kubectl delete svc customers web-frontend
kubectl delete vs.networking.istio customers web-frontend
kubectl delete gateway.networking.istio gateway
kubectl delete authorizationpolicy allow-ingress-frontend allow-web-frontend-customers deny-all
kubectl delete pod curl
四、实战:授权(访问控制)(2)¶
需求:现在我们来编写一个策略,实现 grafana-istio.zhang-qing.com 这个域名只能被限定的来源IP访问。
核心 AuthorizationPolicy 在 ingress-gateway 上进行控制;
[root@master01 istioyaml]# vim grafana-ap.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: istio-ingressgateway-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: ["10.0.0.0/24"]
to:
- operation:
hosts:
- grafana-istio.zhang-qing.com
部署: kubectl apply -f grafana-ap.yaml
[root@master01 istioyaml]# kubectl apply -f grafana-ap.yaml
本地 IP 和 ipBlocks 匹配时:
[root@master01 istioyaml]# curl -I http://grafana-istio.zhang-qing.com
HTTP/1.1 403 Forbidden
本地 IP 和 ipBlocks 不匹配时:
[root@master01 istioyaml]# curl -I http://grafana-istio.zhang-qing.com
HTTP/1.1 200 OK
现在我们可以把想要保护的域名都加入到策略规则中:
[root@master01 istioyaml]# vim grafana-ap.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: istio-ingressgateway-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: ["10.0.0.0/24"]
to:
- operation:
hosts:
- grafana-istio.zhang-qing.com
- prometheus-istio.zhang-qing.com
- kiali-istio.zhang-qing.com
五、"访问控制" 的其它选项¶
5.1 多 istio-ingressgateway¶
一般我们会将“面向用户的业务入口”和“企业内部的职能支撑系统入口”分开。对于前者, ToC 的业务一般不会设置访问控制,ToB 的业务有更大概率会有;后者的访问控制会更 严格一些,且从这个入口进入的规则基本都是一致的——限定仅允许办公网或vpn来源 访问,一般不会对某个具体的应用制定规则。Gateway 定义中通过gateway.spec.selector 选择指定的网格入口。
5.2 在"应用层"实现访问控制¶
这可能会引发异议,因为按照服务网格的初衷,访问控制是网格的职能,应该对业务透 明、无感知,这里却说在应用层去实现访问控制,有误导人的嫌疑。当然这种提法需要 一些前提来支撑,这里更多不是技术层面的问题,而是一种权衡。
-
越是靠近接入层(istio-ingressgateway),我们一般越想保持它的稳定,能不动就 不动,虽然evony提供了很好的热加载能力,可以快速响应变更。
-
变更的风险。在 istio-ingressgateway 上编写授权策略规则,在规则复杂的情况 下,一旦授权策略出现了问题,影响是全接入层的。当然你可以说关注点错误了, 应当去关注保证授权策略的正确性,通过测试,验证流程回归和覆盖。但从风险控 制的角度,把影响控制在局部,仍然是值得考虑的选项。这些都需要根据业务的实
际需求来权衡。比如:在“面向用户的业务入口”上,有一个应用(比如 openapi 这 样的),会开放部分能力给第三方合作伙伴,但小厂限于安全方面的技术实力,出
于风险控制,一般通过限制第三的来源IP + 应用层的认证和授权机制来控制,这种 情况在应用层实现就好些,因为一般会有多个第三方合作伙伴,这些规则一般很
多,会存在大量定制的需求,变更相对频繁;如果是“企业内部的职能支撑系统入 口”,这种在 istio-ingressgateway 上添加规则更合理,因为这种场景,需求本身就是接入层级别的,在应用层实现反而不好,即使是这样,也建议在接入层保持规则 的简单性。很多决策不是技术层的问题,是人的问题。
-
应用层上编写授权策略会面临一些障碍,比如:客户端源 IP 就较麻烦,通过 httpbin 的 /headers api 测试,业务的 sidecar 感知到的 source.ip 有些复杂。它可 能是 istio-ingressgateway 的 Pod 所在节点的 crb0 的 IP(Service 的实现做 snat);根据网络层的实现,情况可能更复杂些。真实的客户端 IP 是通过 header X-Envoy-External-Address 传递的,授权策略通过的 when 配置项提供支持,但是字符串类型的。
-
在应用前增加一层透明代理(如 nginx)来实现访问控制。理论上业务 Pod 里已经有一 个 sidecar 了,不需要另一个“代理”,这里的出发点主要是:在非网络领域,envoy 还是比较陌生,基本还是 nginx 使用得更为广泛,人们对nginx的配置都很熟悉,行 为认知方面都积累了大量的经验。nginx 里对 header 进行变换是很轻松的事情,可 以很容易的应用于 allow、deny 配置项。
六、总结¶
Istio 的 AuthorizationPolicy 是一种强大的资源对象,用于定义和实施访问控制规则。
- 通过使用 标签选择器 和 规则设置,它允许开发人员细粒度地控制特定工作负载的 请求访问权限。
- 可以基于来源命名空间、身份或自定义属性等条件来限制对工作负载的访问。这使 得开发人员能够实现高度定制的权限管理策略,确保只有经过授权的请求才能访问 应用程序。
Istio 的 AuthorizationPolicy 提供了一种强大而灵活的机制,使得开发人员能够轻松定义和实施访问控制策略,以确保微服务架构的安全性和可靠性。