Kubernetes 不再是(只是)好玩的游戏了。它正在被用于生产;它是关键任务;所有旧有的安全和合规规则和法规都需要以某种方式加装到 Kubernetes 上。不幸的是,像 RBAC 这样的旧的访问控制工具根本无法应对挑战。
Kubernetes API 接受了一个与我们大家习惯的 API 范式截然不同的 API。今天的大多数 API 都是所谓的 基于行动的(action-based),这意味着当你想到一个 API 调用时,你正在考虑你想要执行的行动,以改变软件的运行方式。例如,如果你想让一个应用程序暴露在互联网上,你可能会运行 API openport (443),改变应用程序上的网络设置,使端口 443 打开。
相比之下,Kubernetes 有所谓的 基于意图的(intent-based) API(最近在网络领域流行,例如 SDXCentral),这意味着当你想要进行一个 API 调用时,你要考虑的是你希望该系统处于何种状态。你并不关心用什么操作来实现这种希望的状态。你只需告诉系统你想要什么(你的意图),系统就会想出如何实现它 —— 采取哪些动作将系统过渡到期望的状态。例如,你可以说你的应用程序应该运行 1.7 版本的二进制文件,应该使用带加密的持久存储,并且应该连接到互联网。系统会计算出如何升级或降级二进制文件,如何开启加密,以及如何重新配置网络以允许互联网连接。
架构上的关键区别在于,基于意图的 系统既能理解系统当前所处的状态(有时称为 实际状态 ),也能理解你对系统应该处于何种状态的意图(期望状态)。系统不断地计算两者之间的差距,并采取任何必要的行动使实际状态变成期望状态。用户可以直接通过 API 调用来改变期望状态,而依靠系统本身来改变实际状态。
Kubernetes 的 API 是基于意图的。每个 API 调用都允许你指定 Kubernetes 众多对象中的一个对象的期望状态: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 来说" 使之如此 “。
Kubernetes 的 API 模型有几个优势:
基于 Kubernetes 意图的 API 的挑战来自于你想要保护和保障 API 的安全时 —— 当你想要控制哪些人可以使用该 API 做什么时。想象一下,你是 Kubernetes 管理员,负责集群的运维、安全和合规性。新手 Kubernetes 开发人员需要护栏;安全团队需要控制和可见性;合规团队需要帮助将古老的规定映射到这个全新的系统;你从自己的经验中知道你需要采用哪些 Kubernetes 最佳实践。
理想情况下,你会在 Kubernetes 本身内部通过设置访问控制来执行这些规则、法规和最佳实践。基于角色的访问控制(RBAC)是几十年来的解决方案,使你能够控制哪些用户可以在哪些资源上运行哪些 API。Kubernetes RBAC(自 2017 年末 开始提供)是你的第一道防线。它可以让你为特定的用户组提供对资源的只读访问。它让你通过给不同的用户组分配 Kubernetes 的不同部分(也就是 namespace)来隔离不同的用户组(虽然不是完全隔离)。它可以让你限制 service account 的权限。所有这些都是有价值的。
但与基于动作的系统相比,RBAC 处理了绝大部分的访问控制需求,Kubernetes 中的 RBAC 由于其基于意图的 API,提供的控制 要少得多。从 API 的角度来看,只有十几个动作,这意味着如果 alice 可以更新一个资源,她就可以更新这个资源的任何部分。
例如,SRE 需要读取集群中的大部分资源,以便在出现问题时能够诊断出问题。但当 SRE 发现某个节点上出现问题时,例如邻居有噪音,她可能需要对该节点进行排空(drain),以便将工作负载转移到不同的节点上,缓解问题。不幸的是,API 没有 drain 动作 —— 那些是 CLI 提供的宏,只是更新节点上的注释。使用 RBAC 试图达到这个级别的粒度是繁琐而复杂的,以至于不切实际。
下面的基于意图的 K8s RBAC 图从概念上显示了你必须使用 RBAC 的工作内容 —— 你可以选择哪些用户 / 操作 / 资源组合是允许的。
相反,想象一下,如果 Kubernetes 是基于动作的(例如,它包括 cordon、drain、setImage、mountVolume、openPort 等 API)。那么我们就可以使用 RBAC 来授予读以及 cordon 和 drain,但没有其他的功能。基于动作的 API 只是有更多的名字,你可以在编写 RBAC 策略时使用。
简而言之,Kubernetes API 提供了一个强大的、可扩展的、统一的资源模型,但也正是这个资源模型使得 RBAC 对于很多用例来说过于粗粒度。RBAC 所能提供的控制是非常宝贵的,但比起其他系统,RBAC 还远不能满足 Kubernetes 的需要。
那么如果 RBAC 不能提供足够的控制,我们该怎么做呢?我们来看一个例子。“所有的 pod 必须只使用来自受信任的存储库的镜像”(比如说,hooli.com)任何时候有人运行,比如说,kubectl apply,访问控制系统需要根据用户、动作 apply 和描述 pod 的 YAML 做出决定。
kind: Pod
metadata:
labels:
app: nginx
name: nginx-1493591563-bvl8q
namespace: production
spec:
containers:
- image: nginx
name: nginx
securityContext:
privileged: true
- image: hooli.com/frontend
name: frontend
securityContext:
privileged: true
dnsPolicy: ClusterFirst
restartPolicy: Always
为了做出正确的决策,访问控制系统需要提取镜像名称列表(如nginx
和hooli.com/frontend
),并进行字符串操作以提取仓库的名称(如默认的 repo 和 hooli.com)。
一种方案是将一堆关于 Kubernetes 资源的知识构建到访问控制系统本身。然后管理员可以写一个策略,比如谁可以 update-labels
,permitted-image-registries
是什么,等等。这就是大多数系统的做法 —— 发明一堆权限,然后在上面建立一个自定义的访问控制系统。
但是构建一个自定义的访问控制系统对于 Kubernetes 来说是行不通的,因为它允许用户和厂商发明自己的 YAML 格式(自定义资源定义),并安装实现这些格式的代码。所以 Kubernetes 的资源可扩展性要求任何定制的 Kubernetes 访问控制系统本身都是可扩展的。
所以,不管我们做什么,我们都需要一个访问控制系统,让管理员编写策略:
标准的访问控制范式都不能满足这些要求。这包括基于角色的访问控制(RBAC)、基于属性的访问控制(ABAC)、访问控制列表(ACL),甚至是 IAM 风格的策略。
幸运的是,Kubernetes 团队预见到了这个问题,并创建了一个 Admission Control 机制,在这里你可以把控制的范围远远超过 RBAC 和标准的访问控制机制。Kubernetes API 服务器提供了一条访问控制的管道,分为 Authorization(如 RBAC),和 Admission。
授权(Authorization)发生在每次 API 调用上,而准许(Addmission)只发生在更新(创建、更新和删除)上。通过授权,你将获得以下信息以做出决定:
通过 Admission,你会得到一个 YAML 中的 AdmissionReview 对象。它包括所有关于资源被修改的信息,以做出任何你想要的决定(见下面的 request.object
)。
apiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
kind:
group: ''
kind: Pod
version: v1
namespace: frontend
object:
metadata:
creationTimestamp: '2018-10-27T02:12:20Z'
labels:
app: nginx
name: nginx
namespace: frontend
uid: bbfee96d-d98d-11e8-b280-080027868e77
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
volumeMounts:
- mountPath: "/var/run/secrets/kubernetes.io/serviceaccount"
name: default-token-tm9v8
readOnly: true
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: default-token-tm9v8
secret:
secretName: default-token-tm9v8
status:
phase: Pending
qosClass: BestEffort
oldObject:
operation: CREATE
resource:
group: ''
resource: pods
version: v1
uid: bbfeef88-d98d-11e8-b280-080027868e77
userInfo:
groups:
- system:masters
- system:authenticated
username: minikube-user
当然,你可以通过编写、部署和维护实现准入控制 webhook 协议(一个简单的 HTTP/json API)的自定义代码,编写任何你喜欢的逻辑来保护你的 API。现在,如果你不想支持和维护自定义代码,你可以使用 Open Policy Agent 作为 Kubernetes 准入控制器,并利用其声明式策略语言。该语言包括上述所需的表达能力:迭代、点注和 50 多个内置的可用于字符串操纵等。更多信息,请参见 ” 利用 Open Policy Agent 确保 Kubernetes API 安全 " 一文。
在这篇文章中,我们深入研究了 Kubernetes 所面临的 API 安全挑战,并重点介绍了以下几个关键要点:
最后更新于 2024/11/07