1. RBAC–基于角色的访问控制
1.1 Kubernetes基于意图的声明式API
Kubernetes API的设计与大多数现代API不同。它是基于意图的,这意味着使用API的人考虑的是他们想要Kubernetes做什么,而不是如何实现。其结果是一个令人难以置信的可扩展性、弹性,和一个强大而流行的系统。
大多数API都是所谓的基于行动的(action-based),这意味着当你想到一个API调用时,你正在考虑你想要执行的行动,以改变软件的运行方式。
相比之下,Kubernetes有所谓的基于意图的(intent-based)API,这意味着当你想要进行一个API调用时,你要考虑的是你希望该系统处于何种状态。你并不关心用什么操作来实现这种希望的状态。你只需告诉系统你想要什么(你的意图),系统就会想出如何实现它——采取哪些动作将系统过渡到期望的状态。
架构上的关键区别在于,基于意图的系统既能理解系统当前所处的状态(有时称为实际状态),也能理解你对系统应该处于何种状态的意图(期望状态)。系统不断地计算两者之间的差距,并采取任何必要的行动使实际状态变成期望状态。用户可以直接通过API调用来改变期望状态,而依靠系统本身来改变实际状态。
K8s每个API调用都允许指定一个对象的期望状态:pod、service、ingress、configmap等。例如,下面是你为一个nginx工作负载定义的期望状态。
# nginx-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
然后要把这个想要的状态发送到Kubernetes,用kubectl,把上面的YAML文件交给它就行了。
kubectl apply -f nginx-pod.yaml
假设你想改变nginx的版本,挂载一个外部卷,或者提供额外的配置,你更新nginx-pod.yaml文件到任何你想要的状态,然后再使用kubectl apply。更新nginx-pod.yaml文件到任何需要的状态,然后再使用kubectl apply。
kubectl apply -f nginx-pod.yaml
这里的关键要点是,你不是在运行像 updateVersion 或 mountVolume 这样的 API,而是在改变一些描述系统应该处于什么状态的 YAML,并通过运行 apply 来说"使之如此"。
这种方式,使得学习更简单,你只需要学习每个对象的YAML配置格式。相比之下,基于动作的API还需要你学习可能是1,000个动作。
1.2 k8s + rbac的使用
Kubernetes 从 1.6 开始支持基于角色的访问控制机制(Role-Based Access,RBAC),集群管理员可以对用户或服务账号的角色进行更精确的资源访问控制。
要启用 RBAC,请使用 --authorization-mode=RBAC 启动 API Server。
用户可以像使用其他 Kubernetes API 资源一样 (例如通过 kubectl、API 调用等)与RBAC相关的资源进行交互。例如,命令 kubectl create -f (resource).yml
1.2.1 Role 与 ClusterRole
RBAC 授权策略会创建一系列的 Role 和 ClusterRole 来绑定相应的资源实体(serviceAccount 或 group),以此来限制其对集群的操作。
角色可以由命名空间(namespace)内的 Role 对象定义,比如:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # 空字符串"" 表明使用 core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
而整个 Kubernetes 集群范围内有效的角色则通过 ClusterRole 对象实现:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
# 鉴于 ClusterRole 是集群范围对象,所以这里不需要定义 "namespace" 字段
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
1.2.2 RoleBinding 与 ClusterRoleBinding
角色绑定将一个角色中定义的各种权限授予一个或者一组用户。
角色绑定包含了一组相关主体(即 subject, 包括用户 ——User、用户组 ——Group、或者服务账户 ——Service Account)以及对被授予角色的引用。
在命名空间中可以通过 RoleBinding 对象授予权限,而集群范围的权限授予则通过 ClusterRoleBinding 对象完成。
比如,以下角色绑定定义将允许用户 “jane” 从 “default” 命名空间中读取 pod。
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
以下 ClusterRoleBinding 对象允许在用户组 “manager” 中的任何用户都可以读取集群中任何命名空间中的 secret。
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
1.2.3 综合运用rbac
举个例子
- 先创建一个名叫 helm 的 ServiceAccount
- 然后创建相应的 Role 绑定 “tiller-world” namespace,该 Role 只拥有 list pods 的权限
- 最后通过创建 RoleBinding 将该 Role 与之前创建的 ServiceAccount 绑定。
apiVersion: v1
kind: ServiceAccount
metadata:
name: helm
namespace: helm-world
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tiller-user
namespace: tiller-world
rules:
- apiGroups:
- ""
resources:
- pods/portforward
verbs:
- create
- apiGroups:
- ""
resources:
- pods
verbs:
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tiller-user-binding
namespace: tiller-world
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: tiller-user
subjects:
- kind: ServiceAccount
name: helm
namespace: helm-world
1.2.4 rback审计rbac
为了查看每个 Role 的作用以及每个资源对象应该能做哪些事情,比如确认某个 CI/CD Service Account 在指定的 namespace 内是否拥有 Update Pod 的权限。
rback可以用来帮助大家更方便地审计 RBAC。
rback 用来对 kubectl 的输出结果进行可视化展示,可以输出为 .dot 格式,也可以输出为 .png 或任何格式。
kubectl get sa,roles,rolebindings \
-n monitoring -o json \
| rback | dot -Tpng > rback.png
1.3 为什么RBAC不足以保证Kubernetes的API安全?
基于Kubernetes意图的API的挑战来自于你想要保护和保障API的安全时——当你想要控制哪些人可以使用该API做什么时。
Kubernetes中的RBAC由于其基于意图的API。从API的角度来看,只有十几个动作,这意味着如果alice可以更新一个资源,她就可以更新这个资源的任何部分。
相反,如果Kubernetes是基于动作的。那么我们就可以使用RBAC做更细粒度的控制。
简而言之,Kubernetes API提供了一个强大的、可扩展的、统一的资源模型,但也正是这个资源模型使得RBAC对于很多用例来说过于粗粒度。
2. 我们需要什么来保证K8s的API安全?
2.1 Admission机制
我们需要一个访问控制系统,让管理员编写更细粒度的策略.
标准的访问控制范式都不能满足这些要求。这包括基于角色的访问控制(RBAC)、基于属性的访问控制(ABAC)、访问控制列表(ACL),甚至是IAM风格的策略。
幸运的是,Kubernetes团队预见到了这个问题,并创建了一个Admission Control机制,在这里你可以把控制的范围远远超过RBAC和标准的访问控制机制。Kubernetes API服务器提供了一条访问控制的管道,分为Authorization(如RBAC),和Admission
通过Addmission,你将获得以下信息以做出决定:
- 用户:用户、组、认证提供的额外属性。
- 动作:路径、API动词、HTTP动词。
- 资源:资源、子资源、命名空间、API组。
2.2 OPA(Open Policy Agent)
准入控制(Admission Control)赋予了用户控制API的额外权力。你可以使用声明式授权解决方案(如Open Policy Agent)作为Kubernetes Admission Controller,为你提供所需的表达能力,以克服这些新的访问挑战,并提供真正有效的粒度。
开放策略代理(OPA,发音为“ oh-pa”)是一个开放源代码的通用策略引擎。提供了一套统一的框架和语言,用于声明、实施和控制云原生解决方案中各个组件的策略,也是策略即代码(Policy as Code)的经典实现。作为CNCF的毕业项目,目前在CI/CD、Kubernetes、微服务等场景下都有广泛的应用。OPA的架构采用将规则和数据进行分离的解耦设计,不同的角色各司其职,决策系统负责提供决策输入数据。
我们看个例子,假设又一个组织有这些元素:
系统中共有三种组件:
- 服务器:暴露零个或多个协议(例如,http,ssh等)
- 网络:连接服务器,可以是公共的或私有的。公共网络连接到 Internet。
- 端口将服务器连接到网络。
所有服务器、网络和端口都由json描述。
{
"servers": [
{"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]},
{"id": "db", "protocols": ["mysql"], "ports": ["p3"]},
{"id": "cache", "protocols": ["memcache"], "ports": ["p3"]},
{"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]},
{"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]}
],
"networks": [
{"id": "net1", "public": false},
{"id": "net2", "public": false},
{"id": "net3", "public": true},
{"id": "net4", "public": true}
],
"ports": [
{"id": "p1", "network": "net1"},
{"id": "p2", "network": "net3"},
{"id": "p3", "network": "net2"}
]
}
比如,你要实施两条策略
1. Servers reachable from the Internet must not expose the insecure 'http' protocol.
2. Servers are not allowed to expose the 'telnet' protocol.
这两条策略通过OPA的Rego语言实现, 输入则是上面的json数据:
package example
allow = true { # allow is true if...
count(violation) == 0 # there are zero violations.
}
violation[server.id] { # a server is in the violation set if...
some server
public_server[server] # it exists in the 'public_server' set and...
server.protocols[_] == "http" # it contains the insecure "http" protocol.
}
violation[server.id] { # a server is in the violation set if...
server := input.servers[_] # it exists in the input.servers collection and...
server.protocols[_] == "telnet" # it contains the "telnet" protocol.
}
public_server[server] { # a server exists in the public_server set if...
some i, j
server := input.servers[_] # it exists in the input.servers collection and...
server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and...
input.ports[i].network == input.networks[j].id # the port references a network in the input.networks collection and...
input.networks[j].public # the network is public.
}
我们可以通过opa eval命令,验证策略是否生效
# Evaluate a policy on the command line and use the exit code.
./opa eval --fail-defined -i input.json -d example.rego "data.example.violation[x]"
2.3 k8s Admission Controller + OPA
通过将 OPA 部署为admission controller,可以实现如下策略:
- 要求在所有资源上都有特定标签。
- 要求容器镜像来自企业镜像注册中心。
- 要求所有 Pod 指定资源请求和限制。
- 防止创建冲突的 Ingress 对象。
等等
比如:
输入
{
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"request": {
"kind": {
"group": "",
"kind": "Pod",
"version": "v1"
},
"object": {
"metadata": {
"name": "myapp"
},
"spec": {
"containers": [
{
"image": "nginx",
"name": "nginx-frontend"
},
{
"image": "mysql",
"name": "mysql-backend"
}
]
}
}
}
}
OPA + REGO
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Pod"
some i
image := input.request.object.spec.containers[i].image
not startswith(image, "hooli.com/")
msg := sprintf("image '%v' comes from untrusted registry", [image])
}
输出
{
"deny": [
"image 'mysql' comes from untrusted registry",
"image 'nginx' comes from untrusted registry"
]
}