通过本文,我们将学习如何从头开始重新创建 Kubernetes RBAC 授权模型,并了解Roles、ClusterRoles、ServiceAccounts、RoleBindings 和 ClusterRoleBindings 之间的关系。
随着集群中应用程序和资源数量的增加,我们可能需要查看并限制他们相关的权限,从而避免一些危险操作。这些危险操作往往会严重影响生产环境,有时候,甚至会引起长时间的停机,比如过大的权限导致正常运行的服务被删除。
正常情况下,我们可能希望限制生产系统仅仅允许少部分人管理以及访问。
或者,我们可能希望向部署在集群中的维护人员授予一组有限的权限。
我们可以通过Kubernetes 中的基于角色的访问控制 (RBAC) 来实现上述的需求。
Kubernetes API
在讨论RBAC之前,让我们看看授权模型在图中的位置。
假设我们希望将以下 Pod 部署到 Kubernetes 集群:
复制
1. apiVersion: v1
2. kind: Pod
3. metadata:
4. name: my-pod
5. spec:
6. containers:
7. - name: sise
8. image: learnk8s/app:1.0.0
9. ports:
10. - containerPort: 8080
我们可以使用以下命令将 Pod 部署到集群:
复制
1. $ kubectl apply -f pod.yaml
当我们输入时kubectl apply,会触发以下的操作。
kubectl 会:
1、从KUBECONFIG读取配置。
2、从 API 中发现 API 和对象。
3、验证资源客户端(是否有明显的错误?)。
4、将带有有效负载的请求发送到kube-apiserver。
当kube-apiserver收到请求时,它不会立即将其存储在 etcd 中。
首先,它必须验证请求者是否合法。
换句话说,它必须对请求进行身份验证。
一旦通过身份验证,请求者是否有权创建资源?
身份和权限不是一回事。
仅仅因为我们可以访问集群并不意味着我们可以创建或读取所有资源。
授权通常使用基于角色的访问控制 (RBAC)来完成。
借助基于角色的访问控制 (RBAC),我们可以分配精细的权限并限制用户或应用程序可以执行的操作。
在更实际的情况下,API 服务器按顺序执行以下操作:
1、收到请求后,对用户进行身份验证。
a. 当验证失败时,通过返回来拒绝请求401 Unauthorized。
b, 否则,进入下一阶段。
2、用户已通过身份验证,但他们是否有权访问资源
a. 如果他们没有权限,请通过返回来拒绝请求403 Forbidden。
b. 否则,继续。
在本文中,我们将重点关注授权部分。
将用户和权限与 RBAC 角色解耦
RBAC(Role-Based Access Control)即:基于角色的权限控制。通过角色关联用户,角色关联权限的方式间接赋予用户权限。如下图:
有人会问为什么不直接给用户分配权限,还多此一举的增加角色这一环节呢?
其实是可以直接给用户分配权限,只是直接给用户分配权限,少了一层关系,扩展性弱了许多,适合那些用户数量、角色类型少的平台。
对于通常的系统,比如:存在多个用户拥有相同的权限,在分配的时候就要分别为这几个用户指定相同的权限,修改时也要为这几个用户的权限进行一一修改。有了角色后,我们只需要为该角色制定好权限后,将相同权限的用户都指定为同一个角色即可,便于权限管理。
对于批量的用户权限调整,只需调整用户关联的角色权限,无需对每一个用户都进行权限调整,既大幅提升权限调整的效率,又降低了漏调权限的概率。
要了解它是如何工作的,让我们想象一下,我们必须从头开始设计一个授权系统。我们该如何确保用户对特定资源具有写入权限?
一个简单的实现可能涉及编写一个如下所示的列表:
复制
1. | User | Permission | Resource |
2. | ----- | ---------- | -------- |
3. | Bob | read+write | app1 |
4. | Alice | read | app2 |
5. | Mo | read | app2 |
在这个例子中:
· Bob 有读写权限,可以访问app1但无权访问app2。
· Mo 和 Alice对app2有只读权限,但是无权访问app1。
上表适用于少数用户和资源,但一旦开始扩展它就会显示一些限制。
让我们假设Mo和Alice在同一个Team中,并且他们被授予对app1的读取权限。
我们必须将以下条目添加到表中:
复制
1. | User | Permission | Resource |
2. | --------- | ---------- | -------- |
3. | Bob | read+write | app1 |
4. | Alice | read | app2 |
5. | Mo | read | app2 |
6. | Alice | read | app1 |
7. | Mo | read | app1 |
这很好,但Alice和Mo是否有相同的访问权限并不明显,因为他们是同一Team的成员。
在典型的授权系统中我们有用户访问资源。
我们可以直接向用户分配权限,并定义他们可以使用的资源。
这些权限直接映射资源。注意它们是如何作用于用户的。
如果我们决定让第二个用户具有相同的权限,则必须关联相同的权限。
我们可以通过在表中添加“Team”列来解决这个问题,但更好的替代方法是分解关系:
1、我们可以为权限定义一个通用属性:角色。
2、我们可以为定义的组织“Team”进行授权,而不是直接将权限分配给用户。
3、最后,我们可以用户加入到定义组织”Team“。
让我们看看这有什么不同。现在你有两个权限映射关系表:
· 在第一个表中,权限映射到角色
· 在第二个表中,角色与身份相关联
复制
1. | Role | Permission | Resource |
2. | -------- | ---------- | -------- |
3. | admin1 | read+write | app1 |
4. | reviewer | read | app2 |
5.
6. | User | Roles |
7. | ----- | -------- |
8. | Bob | admin1 |
9. | Alice | reviewer |
10. | Mo | reviewer |
当我们希望 Mo 成为 app1 的管理员时那又该如何做了?
我们可以像这样将角色添加到用户:
复制
1. | User | Roles |
2. | ----- | ------------------- |
3. | Bob | admin1 |
4. | Alice | reviewer |
5. | M
我们已经可以想象,将用户与权限与角色分离可以促进拥有大量用户和权限的大型组织中的安全管理。
使用 RBAC 时,我们拥有用户、资源和角色。
权限不会直接分配给用户。相反,他们被包括在这个角色中。
用户通过绑定关联到角色。
由于角色是通用的,当新用户需要访问相同资源时,您可以使用现有角色并将其新绑定关联。
Kubernetes 中的 RBAC
Kubernetes 实现了一个 RBAC 模型(以及其他几个模型) 来保护集群中的资源。
基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对 计算机或网络资源的访问的方法。
RBAC 鉴权机制使用 rbac.authorization.k8s.io API 组 来驱动鉴权决定,允许你通过 Kubernetes API 动态配置策略。
要启用 RBAC,在启动 API 服务器 时将 --authorization-mode 参数设置为一个逗号分隔的列表并确保其中包含 RBAC。
复制
1. kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>
因此 Kubernetes 使用了前面解释过的相同的三个概念:身份、角色和绑定。
它只是用稍微不同的名字来称呼它们。
例如,让我们检查以下授予对 Pod、Service等资源的访问权限所需的 YAML 定义:
复制
1. apiVersion: v1
2. kind: ServiceAccount
3. metadata:
4. name: serviceaccount:app1
5. namespace: demo-namespace
6. ---
7. apiVersion: rbac.authorization.k8s.io/v1
8. kind: Role
9. metadata:
10. name: role:viewer
11. namespace: demo-namespace
12. rules: # Authorization rules for this role
13. - apiGroups: # 1st API group
14. - '' # An empty string designates the core API group.
15. resources:
16. - services
17. - pods
18. verbs:
19. - get
20. - list
21. - apiGroups: # 2nd API group
22. - apiextensions.k8s.io
23. resources:
24. - customresourcedefinitions
25. verbs:
26. - list
27. - apiGroups: # 3rd API group
28. - cilium.io
29. resources:
30. - ciliumnetworkpolicies
31. - ciliumnetworkpolicies/status
32. ---
33. apiVersion: rbac.authorization.k8s.io/v1
34. kind: RoleBinding
35. metadata:
36. name: rolebinding:app1-viewer
37. namespace: demo-namespace
38. roleRef:
39. apiGroup: rbac.authorization.k8s.io
40. kind: Role
41. name: role:viewer
42. subjects:
43. - kind: ServiceAccount
44. name: serviceaccount:app1
45. namespace: demo-namespace
该文件分为三个部分:
1、ServiceAccount——这是访问资源的用户的身份。
2、Role——包含访问资源的权限。
3、将身份(ServiceAccount)关联到权限(Role)的 RoleBinding。
配置提交到集群后,允许使用服务帐户的应用程序向以下端点发出请求:
复制
1. # 1. Kubernetes builtin resources
2. /api/v1/namespaces/{namespace}/services
3. /api/v1/namespaces/{namespace}/pods
4. # 2. A specific API extention provided by cilium.io
5. /apis/cilium.io/v2/namespaces/{namespace}/ciliumnetworkpolicies
6. /apis/cilium.io/v2/namespaces/{namespace}/ciliumnetworkpolicies/status
结果很成功,但是我们忽略了很多细节。
我们究竟授予了哪些资源访问权限?
什么是服务帐户?身份不只是集群中的“用户”吗?
为什么 Role 包含 Kubernetes 对象列表?
为了理解它们是如何工作的,让我们抛开 Kubernetes RBAC 模型并尝试从头开始重建它。
我们将重点关注三个要素:
1、识别和分配身份。
2、’授予权限。
3、将身份与权限相关联。
分配身份:用户,组,robot账户
假设我们的新同事希望登录 Kubernetes 界面。
在这种情况下,我们应该有一个“帐户”或“用户”的实体,每个实体都有一个唯一的名称或 ID(例如电子邮件地址)。
我们应该如何将用户存储在集群中?
Kubernetes 没有代表常规用户帐户的对象。
无法通过 API 调用将用户添加到集群中。
相反,任何提供由集群的证书颁发机构 (CA) 签名的有效证书的参与者都被视为已通过身份验证。
在这种情况下,Kubernetes 从证书的“subject”中的通用名称字段中分配用户名(例如,“/CN=bob”)。
创建一个临时用户信息对象并将其传递给授权 (RBAC) 模块。
深入研究代码会发现一个结构映射了从 Authentication 模块收集的所有详细信息。
复制
1. type User struct {
2. name string // unique for each user
3. ... // other fields
4. }
请注意,User用于集群外的人员或应用。
如果要识别集群中的应用,则应改用服务帐户。
该帐户与普通用户非常相似,但不同之处在于Kubernetes管理它。
服务帐户通常分配给 pod 以授予权限。
例如,我们可以让以下应用程序从集群内部访问资源:
· cilium-agent必须列出特定节点上的所有 pod 资源· ingress nginx控制器必须列出服务的所有后端端点。
对于这些应用,我们可以定义一个 ServiceAccount (SA)。
由于服务帐户是在集群中管理的,因此我们可以使用 YAML 创建它们:
复制
1. apiVersion: v1
2. kind: ServiceAccount
3. metadata:
4. name: sa:app1 # arbitrary but unique string
5. namespace: demo-namespace
为了方便 Kubernetes 管理,我们还可以定义一组User 或ServiceAccount。
通过这样,我可以很方便的引用特定namespace中的所有 ServiceAccount。
现在我们已经定义了如何访问资源,是时候讨论权限了。
此时,我们有一种机制来确定谁可以访问资源。
它可能是一个人、一个robot账户或一群人。
但是他们在集群中访问什么资源呢?
对资源的访问进行建模
在Kubernetes中,我们感兴趣的是控制对资源的访问,比如Pod、Services、Endpoints等。
这些资源通常存储在数据库 (etcd) 中,并通过内置 API 访问,例如:
复制
1. /api/v1/namespaces/{namespace}/pods/{name}
2. /api/v1/namespaces/{namespace}/pods/{name}/log
3. /api/v1/namespaces/{namespace}/serviceaccounts/{name}
限制对这些资源的访问的最佳方法是控制这些 API 端点的请求方式。
为此,我们将需要两件事:
1、资源的 API 端点。
2、授予访问资源的权限类型(例如只读、读写等)。
对于权限,我们将使用verbs,例如get, list, create, patch, delete 等。
想象一下,你想要get, list 以及 watchPods、logs和Services等资源。
我们可以将这些资源和权限组合在一个列表中,如下所示:
复制
1. resources:
2. - /api/v1/namespaces/{namespace}/pods/{name}
3. - /api/v1/namespaces/{namespace}/pods/{name}/log
4. - /api/v1/namespaces/{namespace}/serviceaccounts/{name}
5. verbs:
6. - get
7. - list
8. - watch
根据以下信息,我们可以更简化定义上述信息:
· 基本 URL/api/v1/namespaces/对所有人都是通用的。也许我们可以省略它。
· 我们可以假设所有资源都在当前namespace中并删除{namespace}路径。
最终我们可以简化为:
复制
1. resources:
2. - pods
3. - pods/logs
4. - serviceaccounts
5. verbs:
6. - get
7. - list
8. - watch
该定义更人性化,我们可以很清晰的了解相关权限信息。
不过,权限配置不仅仅只有上面的内容,还有更多的内容。
除了 pod、endpoint、service等内置对象的 API 外,Kubernetes 还支持 API 扩展。
例如,当使用安装 Cilium CNI 时,脚本会创建一个CiliumEndpoint自定义资源 (CR):
复制
1. apiVersion: apiextensions.k8s.io/v1
2. kind: CustomResourceDefinition
3. metadata:
4. name: ciliumendpoints.cilium.io
5. spec:
6. group: cilium.io
7. names:
8. kind: CiliumEndpoint
9. scope: Namespaced
10. # truncated...
这些对象存储在集群中,可通过 kubectl 获得:
复制
1. $ kubectl get ciliumendpoints.cilium.io -n demo-namespace
2. NAME ENDPOINT ID IDENTITY ENDPOINT STATE IPV4
3. IPV6
4. app1 2773 1628124 ready 10.6.7.54
5. app2 3568 1624494 ready 10.6.7.94
6. app3 3934 1575701 ready 10.6.4.24
7. $
可以通过 Kubernetes API 类似地访问自定义资源:
复制
1. /apis/cilium.io/v2/namespaces/{namespace}/ciliumendpoints
2. /apis/cilium.io/v2/namespaces/{namespace}/ciliumendpoints/{name}
如果要将它们映射到 YAML 文件中,可以编写以下内容:
复制
1. resources:
2. - ciliumnetworkpolicies
3. - ciliumnetworkpolicies/status
4. verbs:
5. - get
但是,Kubernetes 怎么知道资源是自定义的呢?
它如何区分使用自定义资源和内置的 API?
不幸的是,从 API endpoint删除 URL 并不是一个好主意。
我们可以通过稍作更改来恢复它。
我们可以在顶部定义它并稍后使用它来扩展资源的 URL。
复制
1. apiGroups:
2. - cilium.io # APIGroup name
3. resources:
4. - ciliumnetworkpolicies
5. - ciliumnetworkpolicies/status
6. verbs:
7. - get
对于没有namespace API的POD之类的资源呢?
Kubernetes “”空API组是一个特殊的组,它引用内置对象。因此,前面的定义应该扩展到:
复制
1. apiGroups:
2. - '' # Built-in objects
3. resources:
4. - pods
5. - pods/logs
6. - serviceaccounts
7. verbs:
8. - get
9. - list
10. - watch
Kubernetes 读取 API 组并自动将它们扩展为:
· 如果为空,则将“”转换为/api/v1/xxx。
· 否则为/apis/{apigroup_name}/{apigroup_version}/xxx。
既然我们知道了如何映射资源和权限,现在终于到了将对多个资源的访问组合在一起的时候了。在Kubernetes中,resources和verbs的集合称为rules,您可以将规则分组到列表中:
复制
1. rules:
2. - rule 1
3. - rule 2
每条规则都包含我们上述所提到的apiGroups,resources和verbs:
复制
1. rules: # Authorization rules
2. - apiGroups: # 1st API group
3. - '' # An empty string designates the core API group.
4. resources:
5. - pods
6. - pods/logs
7. - serviceaccounts
8. verbs:
9. - get
10. - list
11. - watch
12. - apiGroups: # another API group
13. - cilium.io # Custom APIGroup
14. resources:
15. - ciliumnetworkpolicies
16. - ciliumnetworkpolicies/status
17. verbs:
18. - get
规则的集合在 Kubernetes 中具有特定的名称,称为角色。
复制
1. apiVersion: rbac.authorization.k8s.io/v1
2. kind: Role
3. metadata:
4. name: viewer
5. rules: # Authorization rules
6. - apiGroups: # 1st API group
7. - '' # An empty string designates the core API group.
8. resources:
9. - pods
10. - pods/logs
11. - serviceaccounts
12. verbs:
13. - get
14. - list
15. - watch
16. - apiGroups: # another API group
17. - cilium.io # Custom APIGroup
18. resources:
19. - ciliumnetworkpolicies
20. - ciliumnetworkpolicies/status
21. verbs:
22. - get
到目前为止,我们定义了:
· 具有用户、服务帐户和组的身份。
· 对具有角色的资源的权限。
最后,缺少的部分是将两者联系起来。
向用户授予权限
RoleBinding 将角色中定义的权限授予用户、服务帐户或组。让我们看一个例子:
复制
1. apiVersion: rbac.authorization.k8s.io/v1
2. kind: RoleBinding
3. metadata:
4. name: role-binding-for-app1
5. roleRef:
6. apiGroup: rbac.authorization.k8s.io
7. kind: Role
8. name: viewer
9. subjects:
10. - kind: ServiceAccount
11. name: sa-for-app1
12. namespace: kube-system
该定义有两个重要字段:
· 引用viewer角色的roleRef。
· 关联到sa-for-app1服务帐户的subjects。
将资源提交到集群后,使用服务帐户的应用程序或用户将有权访问角色中列出的资源。
如果我们删除绑定,应用程序或用户将失去对这些资源的访问权限(但该角色将随时准备被其他绑定使用)。
请注意该subjects字段是一个包含kind,namespace和name的列表。
该kind属性是从服务帐户和组中识别用户所必需的。
但是namespace了?
将集群拆分为namespace,并将对namespace资源的访问限制为特定帐户,这通常很有帮助。
在大多数情况下,Role和RoleBinding与namespace关联,并授予对特定namespace的访问权。
然而,这两种资源是可以混合使用的——稍后我们将描述该如何混合。
在我们总结理论并从实践开始之前,让我们看一下subjects的几个例子:
复制
1. subjects:
2. - kind: Group
3. name: system:serviceaccounts
4. apiGroup: rbac.authorization.k8s.io
5. # when the namespace field is not specified, this targets all service accounts in all namespace
我们还可以将多个组、用户或服务帐户作为subjects:
复制
1. subjects:
2. - kind: Group
3. name: system:authenticated # for all authenticated users
4. apiGroup: rbac.authorization.k8s.io
5. - kind: Group
6. name: system:unauthenticated # for all unauthenticated users
7. apiGroup: rbac.authorization.k8s.io
回顾一下到目前为止所学的内容,让我们看看如何授予应用程序访问某些自定义资源的权限。
首先,让我们介绍一下场景:你有一个应用程序需要访问Cilium暴露的资源。
想象一下,在集群中部署了一个需要通过 API 访问自定义资源的应用程序。
如果不授予对这些API的访问权限,请求将失败,并显示403禁止的错误消息。
如何授予访问这些资源的权限?
使用服务帐户、角色和角色绑定。
首先,我们应该为我们的工作负载创建一个身份。在 Kubernetes 中,这意味着创建一个服务帐户。
然后,我们需要定义权限并将其包含到角色中。
最后,我们希望通过RoleBinding将身份(服务帐户)关联到权限(角色)。
下次应用程序向Kubernetes API发出请求时,它将被授予访问Cilium资源的权限。
Namespaces和cluster-wide的资源
当我们讨论资源时,您了解到endpoint的结构如下面一样:
复制
1. /api/v1/namespaces/{namespace}/pods/{name}
2. /api/v1/namespaces/{namespace}/pods/{name}/log
3. /api/v1/namespaces/{namespace}/serviceaccounts/{name}
但是没有namespace的资源,比如持久卷和节点呢?
namespace资源只能在namespace内创建,并且该namespace的名称包含在 HTTP 路径中。
如果资源是全局的,比如节点,namespace名称就不会出现在 HTTP 路径中。
复制
1. /api/v1/nodes/{name}
2. /api/v1/persistentvolume/{name}
我们可以将它们添加到角色中吗?
是的,我们可以添加。
毕竟,在引入 Roles 和 RoleBindings 时,我们没有讨论任何namespace的限制。
下面是一个例子:
复制
1. apiVersion: rbac.authorization.k8s.io/v1
2. kind: Role
3. metadata:
4. name: viewer
5. rules: # Authorization rules
6. - apiGroups: # 1st API group
7. - '' # An empty string designates the core API group.
8. resources:
9. - persistentvolumes
10. - nodes
11. verbs:
12. - get
13. - list
14. - watch
但是,如果我们尝试提交该配置并将其关联到服务帐户,大家可能会意识到它不起作用。
持久卷和节点是集群范围的资源。
但是,角色可以授予对namespace范围内资源的访问权限。
如果我们想使用适用于整个集群的 Role,我们可以使用 ClusterRole(以及相应 ClusterRoleBinding的为它分配subject)。
之前的配置应改为:
复制
1. apiVersion: rbac.authorization.k8s.io/v1
2. kind: ClusterRole
3. metadata:
4. name: viewer
5. rules: # Authorization rules
6. - apiGroups: # 1st API group
7. - '' # An empty string designates the core API group.
8. resources:
9. - persistentvolumes
10. - nodes
11. verbs:
12. - get
13. - list
14. - watch
请注意,唯一的变化是kind属性,而其他一切都保持不变。
我们可以使用 ClusterRoles 授予对所有资源的权限——例如,集群中的所有 Pod。
此功能不限于集群范围的资源。
Kubernetes 已经附带了一些角色和集群角色。
让我们来看一下。
复制
1. $ kubectl get roles -n kube-system | grep "^system:"
2. NAME
3. system::leader-locking-kube-controller-manager
4. system::leader-locking-kube-scheduler
5. system:controller:bootstrap-signer
6. system:controller:cloud-provider
7. system:controller:token-cleaner
8. # truncated output...
system:前缀表示资源由集群控制平面直接管理。
此外,所有默认的 ClusterRoles 和 ClusterRoleBindings 都标有kubernetes.io/bootstrapping=rbac-defaults。
让我们也列出 ClusterRoles:
复制
1. $ kubectl get clusterroles -n kube-system | grep "^system:"
2. NAME
3. system:aggregate-to-admin
4. system:aggregate-to-edit
5. system:aggregate-to-view
6. system:discovery
7. system:kube-apiserver
8. system:kube-controller-manager
9. system:kube-dns
10. system:kube-scheduler
11. # truncated output...
我们可以使用以下命令检查每个 Role 和 ClusterRole 的详细信息:
复制
1. $ kubectl get role -n kube-system -o yaml
2. # or
3. $ kubectl get clusterrole -n kube-system -o yaml
至此,我们了解了 Kubernetes RBAC 的基本内容。
通过上述内容,我们学习到了如下内容:
1、如何使用用户、服务帐户和组创建身份。
2、如何为具有角色的namespace中的资源分配权限。
3、如何使用 ClusterRole 为集群资源分配权限。
4、如何将 Roles 和 ClusterRoles 关联到subject。
最后让我们再研究一下几个案例:RBAC的几个不寻常的边缘案例。
理解 Roles、RoleBindings、ClusterRoles 和 ClusterBindings
在较高级别上,Roles 和 RoleBindings集群内部关联,并授予对特定namespace的访问权限,而ClusterRoles和ClusterRoleBinding不属于namespace,并授予对整个集群的访问权限。
但是,可以混合使用这两种类型的资源。
例如,当 RoleBinding 将帐户关联到 ClusterRole 时会发生什么?
接下来让我们通过一些实践来探索这个问题。
让我们使用 minikube 创建一个本地集群:
复制
1. $ minikube start
2.
上一篇:聊聊 Go 语言与云原生技术
下一篇:华为认证的7个企业云战略趋势
¥99.00
¥499.00
¥10500.00
¥499.00