一、前言

在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 (授权策略)的时候,需要考虑以下三个关键部分

  1. 选择目标:明确哪些服务或工作负载需要进行访问控制。可以通过标签选择器、命 名空间或具体的服务名称来指定。
  2. 定义动作:确定要对选定的目标执行的操作。包括允许(ALLOW)、拒绝 (DENY)或中止(ABORT)请求。
  3. 规定规则:定义一组规则来决定请求是否满足授权条件。这些规则可以基于请求的属性(如源 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 优先。下面是具体的规则:

  1. 如果请求匹配任何 DENY 策略,拒绝请求。
  2. 如果工作负载没有任何的 ALLOW 策略,放行请求。
  3. 如果请求匹配任何 ALLOW 策略,放行请求。
  4. 拒绝请求。

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 在"应用层"实现访问控制

这可能会引发异议,因为按照服务网格的初衷,访问控制是网格的职能,应该对业务透 明、无感知,这里却说在应用层去实现访问控制,有误导人的嫌疑。当然这种提法需要 一些前提来支撑,这里更多不是技术层面的问题,而是一种权衡。

  1. 越是靠近接入层(istio-ingressgateway),我们一般越想保持它的稳定,能不动就 不动,虽然evony提供了很好的热加载能力,可以快速响应变更。

  2. 变更的风险。在 istio-ingressgateway 上编写授权策略规则,在规则复杂的情况 下,一旦授权策略出现了问题,影响是全接入层的。当然你可以说关注点错误了, 应当去关注保证授权策略的正确性,通过测试,验证流程回归和覆盖。但从风险控 制的角度,把影响控制在局部,仍然是值得考虑的选项。这些都需要根据业务的实

际需求来权衡。比如:在“面向用户的业务入口”上,有一个应用(比如 openapi 这 样的),会开放部分能力给第三方合作伙伴,但小厂限于安全方面的技术实力,出

于风险控制,一般通过限制第三的来源IP + 应用层的认证和授权机制来控制,这种 情况在应用层实现就好些,因为一般会有多个第三方合作伙伴,这些规则一般很

多,会存在大量定制的需求,变更相对频繁;如果是“企业内部的职能支撑系统入 口”,这种在 istio-ingressgateway 上添加规则更合理,因为这种场景,需求本身就是接入层级别的,在应用层实现反而不好,即使是这样,也建议在接入层保持规则 的简单性。很多决策不是技术层的问题,是人的问题。

  1. 应用层上编写授权策略会面临一些障碍,比如:客户端源 IP 就较麻烦,通过 httpbin 的 /headers api 测试,业务的 sidecar 感知到的 source.ip 有些复杂。它可 能是 istio-ingressgateway 的 Pod 所在节点的 crb0 的 IP(Service 的实现做 snat);根据网络层的实现,情况可能更复杂些。真实的客户端 IP 是通过 header X-Envoy-External-Address 传递的,授权策略通过的 when 配置项提供支持,但是字符串类型的。

  2. 在应用前增加一层透明代理(如 nginx)来实现访问控制。理论上业务 Pod 里已经有一 个 sidecar 了,不需要另一个“代理”,这里的出发点主要是:在非网络领域,envoy 还是比较陌生,基本还是 nginx 使用得更为广泛,人们对nginx的配置都很熟悉,行 为认知方面都积累了大量的经验。nginx 里对 header 进行变换是很轻松的事情,可 以很容易的应用于 allow、deny 配置项。

六、总结

Istio 的 AuthorizationPolicy 是一种强大的资源对象,用于定义和实施访问控制规则。

  • 通过使用 标签选择器规则设置,它允许开发人员细粒度地控制特定工作负载的 请求访问权限。
  • 可以基于来源命名空间、身份或自定义属性等条件来限制对工作负载的访问。这使 得开发人员能够实现高度定制的权限管理策略,确保只有经过授权的请求才能访问 应用程序。

Istio 的 AuthorizationPolicy 提供了一种强大而灵活的机制,使得开发人员能够轻松定义和实施访问控制策略,以确保微服务架构的安全性和可靠性。