10.4 服务与身份认证

上一章

10.6 本章小结

下一章

更多图书

10.5 RBAC访问控制

Istio的授权功能使用的是基于角色的访问权限控制,在服务网格中提供了命名空间级别、服务级别以及方法级别的访问控制功能,具有如下特点:

·基于角色的语法语义,简单易用。

·支持服务到服务和终端用户到服务的授权功能。

·可以自定义属性,这样更灵活,例如在角色和角色绑定上使用条件。

·高性能,Istio授权是在Envoy代理本地实施的。

授权架构如图10-3所示。

管理员通过Istio的配置创建服务访问策略,Pilot接收策略规则后,生成Envoy代理需要的配置格式并分发给对应的Envoy代理,Envoy代理根据访问策略在本地执行服务访问权限的检查,判断服务的请求能否被通过。

图10-3 授权架构(图片来源:Istio官方网站)

管理人员通过编写yaml文件来配置管理授权规则,然后提交配置存储到IstioConfigStore中。Pilot实时监控授权策略,当有授权策略发生改变时,Pilot会立即更新授权策略,然后把授权策略分发给服务实例相关的Envoy代理。每一个Envoy代理会在运行时启动授权引擎,以便在请求到来时进行授权验证,当一个请求到达Envoy代理时,授权引擎以当前的授权策略评估请求,返回通过或者拒绝的授权验证结果。

在Istio中,RBAC访问控制使用Kubernetes中的serviceaccount作为服务的身份标识,用于分配servicerole对象,因此在Kubernetes中部署服务时需要创建对应的serviceaccount,并给部署的deployment指定创建的serviceaccount。

在使用RBAC访问控制,并且使用某些特殊的属性时,比如source.namespace,由于会用到source.principal属性来实现匹配source.namespace属性的功能,因此分配角色的主体对象有时必须是认证的用户或者服务身份标识。当服务没有开启mTLS时,没有经过身份认证,就不能正常应用RBAC访问控制策略。如果使用其他属性,如source.ip、request.headers等,由于不会使用到source.principal属性来实现,就不需要给服务启用mTLS功能。

1.启用授权

通过创建RbacConfig对象,可以启用RBAC授权功能。RbacConfig对象是整个网络中唯一的,且必须命名为default。在RbacConfig对象中可以通过指定mode参数来指定RBAC访问控制启用的范围,mode可取值如下:

·OFF:完全关闭RBAC授权功能。

·ON:为所有的服务和命名空间启用RBAC授权功能。

·ON_WITH_INCLUSION:在指定命名空间或者服务上启用RBAC授权功能。

·ON_WITH_EXCLUSION:在指定命名空间或者服务之外的命名空间或者服务中启用RBAC授权功能。

使用示例如下。

在default命名空间启用RBAC授权功能:


apiVersion: rbac.istio.io/v1alpha1
kind: RbacConfig
metadata:
  name: default
spec:
  mode: 'ON_WITH_INCLUSION'
  inclusion:
    namespaces: 
    - default

在istio-system和kube-system之外的命名空间中启用RBAC授权功能:


apiVersion: rbac.istio.io/v1alpha1
kind: RbacConfig
metadata:
  name: default
spec:
  mode: 'ON_WITH_EXCLUSION'
  exclusion:
    namespaces: 
    - istio-system
    - kube-system

2.授权策略

RBAC授权策略由ServiceRole和ServiceRoleBinding组成,ServiceRole中包含了服务访问权限的集合,ServiceRoleBinding把ServiceRole分配给指定的对象,如用户和服务。

ServiceRole对象中包含了字段为rules的权限列表,每一个rule有如下字段:

·services:服务列表,可以设置为*表示指定命名空间的所有服务。

·methods:HTTP的方法列表,在gRPC请求中该字段会被忽略,因为gRPC中只有POST方法,可以设置为*表示不限定方法。

·paths:HTTP的请求URI列表或者gRPC请求中的方法列表,gRPC方法必须是/packageName.serviceName/methodName这种格式,并且是大小写敏感的。URI支持前缀、后缀和精确匹配,例如:/books/review精确匹配/books/review,前缀匹配/books/,后缀匹配/review。如果没有指定此字段表示对任何URI或者gRPC请求中的方法都生效。

·constraints:表示特殊限制列表,比如限制服务版本、请求头等信息 [1]

rule中所有的字段均支持前缀和后缀匹配。

定义命名空间级别的ServiceRole示例如下:


1 apiVersion: rbac.istio.io/v1alpha1
 2 kind: ServiceRole
 3 metadata:
 4   name: service-viewer
 5   namespace: default
 6 spec:
 7   rules:
 8   - services: ["*"]
 9     methods: ["GET"]
10     constraints:
11     - key: "destination.labels[app]"
12       values:
13       - service-js
14       - service-python
15       - service-lua
16       - service-node
17       - service-go

如上的ServiceRole定义表示default命名空间的所有服务都有GET方法的权限,但是第10~17行又定义了特殊的限制,限制请求的目标地址只能为values中指定的服务,访问其他服务时没有权限。

使用前缀和后缀匹配示例如下:


apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:
  name: tester
  namespace: default
spec:
  rules:
  - services: ["test-*"]
    methods: ["*"]
  - services: ["bookstore.default.svc.cluster.local"]
    paths: ["*/reviews"]
    methods: ["GET"]

ServiceRoleBinding的定义包含如下两个字段:

·roleRef指定某个同命名空间的ServiceRole名字。

·subjects指定角色分配的主体对象列表。主体可以是user,也可以是properties,还可以是两者的组合。user的值一般是用户的标识或者服务的身份标识,也就是Pod使用的serviceaccount名称,使用如"cluster.local/ns/default/sa/service-python"这样的形式。properties与ServiceRole中的constraints字段的使用方法类似 [2]

定义命名空间级别的ServiceRoleBinding示例如下:


apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRoleBinding
metadata:
  name: bind-service-viewer
  namespace: default
spec:
  subjects:
  - properties:
      source.namespace: "istio-system"
  - properties:
      source.namespace: "default"
  roleRef:
    kind: ServiceRole
    name: "service-viewer"

如上代码中的ServiceRoleBinding定义,将名为service-viewer的ServiceRole分配给请求的来源命名空间为istio-system和default的请求主体。

分配角色给所有用户示例:


apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRoleBinding
metadata:
  name: bind-service-js-service-python-viewer
  namespace: default
spec:
  subjects:
  - user: "*"
  roleRef:
    kind: ServiceRole
    name: "service-js-service-python-viewer"

分配角色给所有认证的用户示例:


apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRoleBinding
metadata:
  name: bind-service-js-service-python-viewer
  namespace: default
spec:
  subjects:
  - properties:
      source.principal: "*"
  roleRef:
    kind: ServiceRole
    name: "service-js-service-python-viewer"

基于服务级别的访问控制策略使用示例:


1 apiVersion: rbac.istio.io/v1alpha1
 2 kind: ServiceRole
 3 metadata:
 4   name: service-js-service-python-viewer
 5   namespace: default
 6 spec:
 7   rules:
 8   - services:
 9     - "service-js.default.svc.cluster.local"
10     - "service-python.default.svc.cluster.local"
11     methods: ["GET"]
12 ---
13 apiVersion: rbac.istio.io/v1alpha1
14 kind: ServiceRoleBinding
15 metadata:
16   name: bind-service-js-service-python-viewer
17   namespace: default
18 spec:
19   subjects:
20   - user: "*"
21   roleRef:
22     kind: ServiceRole
23     name: "service-js-service-python-viewer"
24 ---
25 apiVersion: rbac.istio.io/v1alpha1
26 kind: ServiceRole
27 metadata:
28   name: service-lua-service-node-viewer
29   namespace: default
30 spec:
31   rules:
32   - services:
33     - "service-lua.default.svc.cluster.local"
34     - "service-node.default.svc.cluster.local"
35     methods: ["GET"]
36 ---
37 apiVersion: rbac.istio.io/v1alpha1
38 kind: ServiceRoleBinding
39 metadata:
40   name: bind-service-lua-service-node-viewer
41   namespace: default
42 spec:
43   subjects:
44   - user: "cluster.local/ns/default/sa/service-python"
45   roleRef:
46     kind: ServiceRole
47     name: "service-lua-service-node-viewer"
48 ---
49 apiVersion: rbac.istio.io/v1alpha1
50 kind: ServiceRole
51 metadata:
52   name: service-go-viewer
53   namespace: default
54 spec:
55   rules:
56   - services: ["service-go.default.svc.cluster.local"]
57     methods: ["GET"]
58 ---
59 apiVersion: rbac.istio.io/v1alpha1
60 kind: ServiceRoleBinding
61 metadata:
62   name: bind-service-go-viewer
63   namespace: default
64 spec:
65   subjects:
66   - user: "cluster.local/ns/default/sa/service-node"
67   roleRef:
68     kind: ServiceRole
69     name: "service-go-viewer"

第1~23行定义的访问控制策略表明,服务service-js和服务service-python可以被任何用户以GET方法访问。

第25~47行定义的访问控制策略表明,服务service-node和服务service-lua只可以被default命名空间中使用了名为service-python的serviceaccount的服务实例以GET方法访问。

第49~69行定义的访问控制策略表明,服务service-go只可以被default命名空间中使用了名为service-node的serviceaccount的服务实例以GET方法访问。

【实验】

1)部署服务:


$ kubectl apply -f service/js/service-go.yaml
$ kubectl apply -f service/js/service-node.yaml
$ kubectl apply -f service/lua/service-lua.yaml
$ kubectl apply -f service/python/service-python.yaml
$ kubectl apply -f service/js/service-js.yaml

2)启用default命名空间的mTLS功能。

RBAC的部分实验需要服务身份认证,需要开启mTLS:


$ kubectl apply -f istio/security/mtls-namespace-default-on.yaml

3)创建Gateway和service-js路由规则。

创建Gateway和服务service-js的mTLS路由规则,并路由到v1版本:


$ kubectl apply -f istio/route/gateway-js-v1-mtls.yaml

4)替换service-node服务和service-python服务的部署为带有serviceaccount的Deployment:


serviceaccount

Deployment


$ kubectl apply -f service/node/service-node-with-serviceaccount.yaml
$ kubectl apply -f service/python/service-python-with-serviceaccount.yaml

5)创建测试Pod:


$ kubectl apply -f kubernetes/dns-test.yaml

6)访问服务:


$ kubectl exec dns-test -c dns-test -- curl -s http://service-go/env
{"message":"go v2"}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-node/env
{"message":"node v1","upstream":[{"message":"go v2","response_time":"0.40"}]}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-lua/env
{"message":"lua v2"}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-python/env
{"message":"python v1","upstream":[{"message":"lua v1","response_time":0.22},{"message":"node v1","response_time":0.47,"upstream":[{"message":"go v1","response_time":"0.24"}]}]}

此时服务开启了mTLS,所有服务访问正常。

7)浏览器访问。

浏览器访问地址为http://11.11.11.112:31380/ ,服务正常如图10-4所示。

8)启动default命名空间的RBAC访问控制:


$ kubectl apply -f istio/security/rbac-config-on.yaml

9)访问服务:


$ kubectl exec dns-test -c dns-test -- curl -s http://service-go/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-node/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-lua/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-python/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-js/env
RBAC: access denied

图10-4 RBAC访问控制实验(一)

此时由于开启了RBAC访问控制,而没有创建授权策略,所有服务访问都会被拒绝。

10)创建RBAC命名空间级别规则:


$ kubectl apply -f istio/security/rbac-namespace-policy.yaml

11)访问服务:


$ kubectl exec dns-test -c dns-test -- curl -s http://service-go/env
{"message":"go v1"}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-node/env
{"message":"node v2","upstream":[{"message":"go v1","response_time":"0.02"}]}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-lua/env
{"message":"lua v1"}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-python/env
{"message":"python v1","upstream":[{"message":"lua v2","response_time":0.06},{"message":"node v2","response_time":0.13,"upstream":[{"message":"go v1","response_time":"0.02"}]}]}

此时已经创建了授权策略,所有服务访问正常。

12)浏览器访问。

浏览器访问地址为http://11.11.11.112:31380/ ,服务正常如图10-5所示。

图10-5 RBAC访问控制实验(二)

13)删除命名空间级别的授权:


$ kubectl delete -f istio/security/rbac-namespace-policy.yaml

14)创建服务级别的授权:


$ kubectl apply -f istio/security/rbac-service-policy.yaml

15)访问服务:


$ kubectl exec dns-test -c dns-test -- curl -s http://service-go/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-node/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-lua/env
RBAC: access denied
$ kubectl exec dns-test -c dns-test -- curl -s http://service-python/env
{"message":"python v2","upstream":[{"message":"lua v1","response_time":0.3},{"message":"node v2","upstream":[{"message":"go v2","response_time":"0.02"}],"response_time":0.25}]}
$ kubectl exec dns-test -c dns-test -- curl -s http://service-js/
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>React App</title><link href="/static/css/main.c17080f1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script type="text/javascript" src="/static/js/main.bc334d90.js"></script></body></html>

以上步骤创建了服务级别的授权,只允许指定服务调用,因此我们使用dns-test访问时只有service-python服务和service-js服务可以正常访问,因为服务service-python与服务service-js允许任何用户调用。

16)浏览器访问。

浏览器访问地址为http://11.11.11.112:31380/ ,服务正常如图10-6所示。

17)清理:


$ kubectl delete -f kubernetes/dns-test.yaml
$ kubectl delete -f istio/security/rbac-config-on.yaml
$ kubectl delete -f istio/security/rbac-service-policy.yaml
$ kubectl delete -f istio/route/gateway-js-v1-mtls.yaml
$ kubectl delete -f istio/security/mtls-namespace-default-on.yaml

图10-6 RBAC访问控制实验(三)

[1] 可以使用的值参考官方文档https://istio.io/docs/reference/config/authorization/constraints-and-properties/#constraints。

[2] 具体可以使用的值参考官方文档https://istio.io/docs/reference/config/authorization/constraints-and-properties/#properties。