修复 Kubernetes 集群中的漏洞。 DevOpsConf 的报告和文字记录

南桥解决方案架构师兼 Slurm 教师 Pavel Selivanov 在 DevOpsConf 2019 上发表了演讲。本次演讲是 Kubernetes 深入课程“Slurm Mega”主题之一的一部分。

Slurm 基础:Kubernetes 简介 18 月 20 日至 XNUMX 日在莫斯科举行。
Slurm Mega:深入了解 Kubernetes — 莫斯科,22 月 24 日至 XNUMX 日。
Slurm Online:两门 Kubernetes 课程 始终可用。

剪辑下方是报告的文字记录。

同事们以及所有同情他们的人们下午好。 今天我要讲的是安全。

我看到今天大厅里有很多保安。 如果我使用的安全领域术语与您的习惯不完全一样,我提前向您道歉。

大约六个月前,我碰巧遇到了一个公共 Kubernetes 集群。 公共意味着有第 n 个命名空间;在这些命名空间中,有在其命名空间中隔离的用户。 所有这些用户都属于不同的公司。 嗯,假设这个集群应该用作 CDN。 也就是说,他们给你一个集群,给你一个用户,你去那里你的命名空间,部署你的前端。

我以前的公司试图销售这样的服务。 我被要求戳一下集群,看看这个解决方案是否合适。

我来到了这个集群。 我被赋予了有限的权利和有限的命名空间。 那里的人明白什么是安全。 他们了解了 Kubernetes 中基于角色的访问控制 (RBAC),并对其进行了扭曲,这样我就无法从部署中单独启动 Pod。 我不记得我试图通过启动一个没有部署的 Pod 来解决什么问题,但我真的只想启动一个 Pod。 为了好运,我决定看看我在集群中拥有哪些权利,我能做什么,不能做什么,以及他们在那里搞砸了什么。 同时,我会告诉你他们在 RBAC 中配置错误的地方。

碰巧的是,在两分钟内,我收到了他们集群的管理员,查看了所有相邻的命名空间,看到了已经购买了服务并部署的公司正在运行的生产前端。 我几乎无法阻止自己走到某人的前面并在主页上发表一些脏话。

我将通过示例告诉您我是如何做到这一点以及如何保护自己免受这种情况的侵害。

但首先,让我介绍一下我自己。 我叫帕维尔·塞利瓦诺夫。 我是南桥的一名建筑师。 我了解 Kubernetes、DevOps 和各种奇特的东西。 我和南桥工程师正在构建这一切,并且我正在提供咨询服务。

除了我们的主要活动之外,我们最近还启动了名为 Slurms 的项目。 我们正在努力将我们使用 Kubernetes 的能力带给大众,教会其他人也使用 K8s。

今天我要讲什么? 报告的主题很明显——关于 Kubernetes 集群的安全性。 但我想说的是,这个话题非常大——因此我想立即澄清我绝对不会谈论的内容。 我不会谈论那些已经在互联网上使用过上百次的陈词滥调术语。 各种 RBAC 和证书。

我将讨论 Kubernetes 集群中的安全性让我和我的同事感到痛苦的地方。 我们在提供 Kubernetes 集群的提供商和来找我们的客户中都看到了这些问题。 甚至来自其他咨询管理公司的客户。 也就是说,这场悲剧的规模其实是非常大的。

今天我主要讲三点:

  1. 用户权限与 Pod 权限。 用户权限和 Pod 权限不是一回事。
  2. 收集有关集群的信息。 我将展示您可以从集群中收集所需的所有信息,而无需在此集群中拥有特殊权限。
  3. 对集群的 DoS 攻击。 如果我们无法收集信息,无论如何我们都可以放置一个集群。 我将讨论针对集群控制元素的 DoS 攻击。

我要提到的另一件事是我对所有这些进行了测试,我可以肯定地说这一切都有效。

我们以使用 Kubespray 安装 Kubernetes 集群为基础。 如果有人不知道的话,这实际上是 Ansible 的一组角色。 我们在工作中经常使用它。 好处是你可以把它滚到任何地方——你可以把它滚到铁片上或云中的某个地方。 原则上,一种安装方法适用于所有情况。

在这个集群中,我将拥有 Kubernetes v1.14.5。 我们将考虑的整个 Cube 集群被划分为多个命名空间,每个命名空间属于一个单独的团队,并且该团队的成员可以访问每个命名空间。 他们不能去不同的命名空间,只能去他们自己的命名空间。 但有一个管理员帐户拥有整个集群的权限。

修复 Kubernetes 集群中的漏洞。 DevOpsConf 的报告和文字记录

我承诺我们要做的第一件事就是获得集群的管理员权限。 我们需要一个专门准备的 pod 来破坏 Kubernetes 集群。 我们需要做的就是将其应用到 Kubernetes 集群中。

kubectl apply -f pod.yaml

该 Pod 将到达 Kubernetes 集群的主节点之一。 此后,集群将愉快地向我们返回一个名为 admin.conf 的文件。 在Cube中,这个文件存储了所有管理员证书,同时配置了集群API。 我认为,98% 的 Kubernetes 集群的管理员访问权限就是如此简单。

我再说一遍,这个 pod 是由集群中的一位开发人员创建的,他有权将他的建议部署到一个小型命名空间中,它全部由 RBAC 限制。 他没有权利。 但无论如何,证书还是被退回了。

现在介绍一个专门准备的豆荚。 我们在任何图像上运行它。 我们以 debian:jessie 为例。

我们有这个东西:

tolerations:
-   effect: NoSchedule 
    operator: Exists 
nodeSelector: 
    node-role.kubernetes.io/master: "" 

什么是宽容? Kubernetes 集群中的 master 通常会被标记为“污点”。 而这个“感染”的本质就是说pod不能分配给master节点。 但没有人愿意在任何豆荚中表明它对“感染”具有耐受​​性。 容忍部分只是说,如果某个节点有 NoSchedule,那么我们的节点可以容忍这种感染 - 并且没有问题。

进一步说,我们的下属不仅要宽容,而且还要专门针对主人。 因为大师们拥有我们需要的最美味的东西——所有的证书。 因此,我们说 nodeSelector - 我们在 master 上有一个标准标签,它允许您从集群中的所有节点中准确地选择那些作为 master 的节点。

有了这两段,他就一定能到大师的境界了。 他将被允许住在那里。

但仅仅来到大师身边对我们来说还不够。 这不会给我们任何东西。 接下来我们有两件事:

hostNetwork: true 
hostPID: true 

我们指定我们启动的 pod 将位于内核命名空间、网络命名空间和 PID 命名空间中。 一旦 pod 在 master 上启动,它将能够看到该节点的所有真实的、实时的接口,侦听所有流量并查看所有进程的 PID。

然后就是一些小事了。 拿起 etcd 并阅读你想要的内容。

最有趣的是 Kubernetes 功能,它默认存在。

volumeMounts:
- mountPath: /host 
  name: host 
volumes:
- hostPath: 
    path: / 
    type: Directory 
  name: host 

它的本质是,我们可以在我们启动的 pod 中说,即使没有这个集群的权限,我们想要创建一个 hostPath 类型的卷。 这意味着从我们将启动的主机获取路径 - 并将其作为卷。 然后我们称之为名称:主机。 我们将整个 hostPath 挂载到 pod 内。 在此示例中,到 /host 目录。

我再重复一遍。 我们告诉 pod 来到 master,获取那里的 hostNetwork 和 hostPID - 并将 master 的整个根挂载到这个 pod 中。

你知道在 Debian 中我们运行着 bash,并且这个 bash 在 root 下运行。 也就是说,我们只是在 master 上获得了 root 权限,而在 Kubernetes 集群中没有任何权限。

然后整个任务就是进入子目录/host /etc/kubernetes/pki,如果我没记错的话,在那里获取集群的所有主证书,并相应地成为集群管理员。

如果您这样看,这些是 pod 中最危险的一些权限 - 无论用户拥有什么权限:
修复 Kubernetes 集群中的漏洞。 DevOpsConf 的报告和文字记录

如果我有权在集群的某个命名空间中运行某个 pod,那么该 pod 默认就具有这些权限。 我可以运行特权 Pod,这些通常都是所有权限,实际上是节点上的 root。

我最喜欢的是 Root 用户。 Kubernetes 有这个 Run As Non-Root 选项。 这是一种针对黑客的保护。 你知道什么是“摩尔达维亚病毒”吗? 如果你突然成为一名黑客并来到我的 Kubernetes 集群,那么我们这些可怜的管理员会问:“请在你的 Pod 中指明你将使用哪个 Pod 来攻击我的集群,以非 root 身份运行。 否则,如果你在 root 下运行你的 pod 中的进程,你就很容易黑我。 请保护好自己,远离自己。”

在我看来,主机路径卷是从 Kubernetes 集群获取所需结果的最快方法。

但这一切该怎么办呢?

任何遇到 Kubernetes 的普通管理员都应该想到:“是的,我告诉过你,Kubernetes 不起作用。 里面有洞。 整个立方体都是废话。” 事实上,有文档这样的东西,如果你看那里,有一个部分 Pod 安全策略.

这是一个 yaml 对象 - 我们可以在 Kubernetes 集群中创建它 - 它专门在 pod 的描述中控制安全方面。 也就是说,事实上,它控制着启动时 pod 中的任何 hostNetwork、hostPID、某些卷类型的使用权限。 借助 Pod 安全策略,这一切都可以被描述。

关于 Pod 安全策略最有趣的事情是,在 Kubernetes 集群中,所有 PSP 安装程序不仅没有以任何方式进行描述,而且默认情况下只是禁用的。 Pod 安全策略是使用准入插件启用的。

好的,让我们将 Pod 安全策略部署到集群中,假设我们在命名空间中有一些服务 Pod,只有管理员可以访问这些服务 Pod。 假设在所有其他情况下,pod 的权利有限。 因为很可能开发人员不需要在集群中运行特权 Pod。

我们似乎一切都很好。 而且我们的 Kubernetes 集群不可能在两分钟内被黑客攻击。

有一个问题。 如果您有 Kubernetes 集群,那么很可能会在您的集群上安装监控功能。 我什至预测,如果你的集群有监控,它将被称为 Prometheus。

我要告诉您的内容对于 Prometheus 操作员和以其纯粹形式交付的 Prometheus 都有效。 问题是,如果我不能这么快让管理员进入集群,那么这意味着我需要进行更多检查。 我可以借助你的监控进行搜索。

可能每个人都读过关于 Habré 的相同文章,并且监控位于监控命名空间中。 Helm Chart 对于每个人来说叫法都大致相同。 我猜如果你安装了 stable/prometheus,你最终会得到大致相同的名称。 而且很可能我什至不必猜测集群中的 DNS 名称。 因为它是标准的。

修复 Kubernetes 集群中的漏洞。 DevOpsConf 的报告和文字记录

接下来我们有一个特定的 dev ns,您可以在其中运行特定的 pod。 然后从这个 Pod 中可以很容易地执行以下操作:

$ curl http://prometheus-kube-state-metrics.monitoring 

prometheus-kube-state-metrics 是 Prometheus 导出器之一,它从 Kubernetes API 本身收集指标。 那里有很多数据,你的集群中正在运行什么,它是什么,你遇到了什么问题。

举个简单的例子:

kube_pod_container_info{namespace=“kube-system”,pod=“kube-apiserver-k8s-1”,container=“kube-apiserver”,image=

“gcr.io/google-containers/kube-apiserver:v1.14.5”

,image_id=»docker-pullable://gcr.io/google-containers/kube- apiserver@sha256:e29561119a52adad9edc72bfe0e7fcab308501313b09bf99df4a96 38ee634989″,container_id=»docker://7cbe7b1fea33f811fdd8f7e0e079191110268f2 853397d7daf08e72c22d3cf8b»} 1

通过从非特权 Pod 发出简单的卷曲请求,您可以获得以下信息。 如果您不知道正在运行哪个版本的 Kubernetes,它会很容易地告诉您。

最有趣的是,除了访问 kube-state-metrics 之外,您还可以轻松地直接访问 Prometheus 本身。 您可以从那里收集指标。 您甚至可以从那里构建指标。 即使从理论上讲,您也可以从 Prometheus 中的集群构建这样的查询,这只会将其关闭。 并且您的集群监控将完全停止工作。

这里出现的问题是是否有任何外部监控监控您的监控。 我刚刚获得了在 Kubernetes 集群中进行操作的机会,并且没有给自己带来任何后果。 你甚至不知道我在那里工作,因为不再有任何监控。

就像 PSP 一样,问题在于所有这些花哨的技术——Kubernetes、Prometheus——它们根本不起作用,而且漏洞百出。 并不真地。

有这么一件事—— 网络政策.

如果您是一名普通管理员,那么您很可能知道网络策略,这只是另一个 yaml,集群中已经有很多这样的 yaml。 而且有些网络策略是绝对不需要的。 即使你读到 Network Policy 是什么,它是 Kubernetes 的 yaml 防火墙,它允许你限制命名空间之间、pod 之间的访问权限,那么你肯定会认为 Kubernetes 中 yaml 格式的防火墙是基于下一个抽象……不,不。 这绝对是没有必要的。

即使您没有告诉安全专家,使用 Kubernetes,您也​​可以构建一个非常简单且非常细粒度的防火墙。 如果他们还不知道这一点并且不打扰您:“好吧,给我,给我......”那么无论如何,您需要网络策略来阻止对某些可以从集群中拉出的服务位置的访问未经任何授权。

正如我给出的示例所示,您可以从 Kubernetes 集群中的任何命名空间提取 kube 状态指标,而无需任何权限。 网络策略已关闭从所有其他命名空间到监视命名空间的访问,就是这样:没有访问,没有问题。 在所有现有的图表中,无论是标准的 Prometheus 还是 Operator 中的 Prometheus,helm 值中都只有一个选项,可以简单地为它们启用网络策略。 您只需将其打开,它们就会工作。

这里确实有一个问题。 作为一名普通的留着胡子的管理员,您很可能认为不需要网络策略。 在阅读了各种有关 Habr 等资源的文章后,您认为 flannel(尤其是主机网关模式)是您可以选择的最佳选择。

怎么办?

您可以尝试重新部署 Kubernetes 集群中的网络解决方案,尝试将其替换为功能更强大的解决方案。 例如,对于同一个印花布。 但我想说,在 Kubernetes 工作集群中更改网络解决方案的任务非常重要。 我解决了两次(不过,都是理论上的),但我们甚至在 Slurms 中展示了如何做到这一点。 对于我们的学生,我们展示了如何更改 Kubernetes 集群中的网络解决方案。 原则上可以尽量保证生产集群不宕机。 但你可能不会成功。

而问题其实解决起来也很简单。 集群中有证书,并且您知道您的证书将在一年后过期。 好吧,通常是在集群中使用证书的正常解决方案 - 我们为什么要担心,我们会在附近建立一个新集群,让旧集群腐烂,然后重新部署所有内容。 确实,当它腐烂时,我们将不得不坐一天,但这是一个新的簇。

当你提出一个新的集群时,同时插入 Calico 而不是 flannel。

如果您的证书已颁发一百年并且您不打算重新部署集群怎么办? 有一种叫做 Kube-RBAC-Proxy 的东西。 这是一个非常酷的开发,它允许您将自己作为 sidecar 容器嵌入到 Kubernetes 集群中的任何 pod 中。 而它实际上是通过Kubernetes本身的RBAC来给这个pod添加授权的。

有一个问题。 此前,这个 Kube-RBAC-Proxy 解决方案内置于运营商的 Prometheus 中。 但后来他就走了。 现在,现代版本依赖于您拥有网络策略并使用它们关闭它的事实。 因此我们必须稍微重写一下图表。 事实上,如果你去 这个存储库,有如何使用它作为 sidecar 的示例,并且图表将必须最少地重写。

还有一个小问题。 Prometheus 并不是唯一一个向任何人提供其指标的公司。 我们所有的 Kubernetes 集群组件也能够返回自己的指标。

但正如我已经说过的,如果你无法访问集群并收集信息,那么你至少会造成一些伤害。

因此,我将快速展示两种破坏 Kubernetes 集群的方法。

当我告诉你这个时,你会笑,这是两个现实生活中的案例。

方法一。 资源枯竭。

让我们启动另一个特殊的 Pod。 它将有一个这样的部分。

resources: 
    requests: 
        cpu: 4 
        memory: 4Gi 

如您所知,请求数是主机上为具有请求的特定 Pod 保留的 CPU 和内存量。 如果我们在 Kubernetes 集群中有一个四核主机,并且有四个 CPU Pod 带着请求到达那里,这意味着没有更多带有请求的 Pod 能够到达该主机。

如果我运行这样的 pod,那么我将运行以下命令:

$ kubectl scale special-pod --replicas=...

那么其他人将无法部署到 Kubernetes 集群。 因为所有节点都会耗尽请求。 因此我将停止您的 Kubernetes 集群。 如果我在晚上这样做,我可以停止部署很长一段时间。

如果我们再看一下 Kubernetes 文档,我们会看到这个叫做 Limit Range 的东西。 它为集群对象设置资源。 你可以在 yaml 中编写一个 Limit Range 对象,将其应用到某些命名空间 - 然后在这个命名空间中你可以说你有 pod 的默认、最大和最小资源。

借助这样的东西,我们可以限制团队特定产品命名空间中的用户在其 Pod 上指示各种令人讨厌的事情的能力。 但不幸的是,即使你告诉用户他们无法启动请求多个 CPU 的 pod,但有一个如此美妙的缩放命令,或者他们可以通过仪表板进行缩放。

这就是第二种方法的由来。 我们发射了 11 个 Pod。 那是一百一十亿。 这并不是因为我想出了这样的数字,而是因为我亲眼所见。

真实的故事。 傍晚时分,我正要离开办公室。 我看到一群开发人员坐在角落里,疯狂地用笔记本电脑做一些事情。 我走到他们面前问:“你们怎么了?”

早些时候,晚上九点左右,一位开发商正准备回家。 我决定:“我现在将我的应用程序缩小到一个。” 我按了一下,但网速有点慢。 他又按了一个,又按了一个,然后按下了Enter键。 我尽我所能地探寻一切。 然后互联网诞生了——一切都开始缩小到这个数字。

确实,这个故事并不是发生在 Kubernetes 上;当时是 Nomad。 结果是,在我们尝试阻止 Nomad 持续尝试扩容一个小时后,Nomad 回复说他不会停止扩容,也不会做任何其他事情。 “我累了,我走了。” 他蜷缩起来。

当然,我也尝试在 Kubernetes 上做同样的事情。 Kubernetes 对 1 亿个 Pod 并不满意,他说:“我不能。 超过内部护齿套。” 但 000 个 Pod 就可以。

为了应对十亿,立方体并没有退出。 他真的开始扩展了。 这个过程越深入,他创建新豆荚所需的时间就越多。 但这个过程仍在继续。 唯一的问题是,如果我可以在我的命名空间中无限制地启动 pod,那么即使没有请求和限制,我也可以启动许多带有某些任务的 pod,在这些任务的帮助下,节点将开始在内存和 CPU 中累加。 当我启动这么多 Pod 时,它们的信息应该进入存储,即 etcd。 当太多信息到达那里时,存储开始返回太慢——Kubernetes 开始变得迟钝。

还有一个问题...如您所知,Kubernetes 控制元素不是一个核心元素,而是多个组件。 特别是有控制器管理器、调度器等。 所有这些人都会同时开始做不必要的、愚蠢的工作,随着时间的推移,这些工作将开始花费越来越多的时间。 控制器管理器将创建新的 Pod。 调度程序将尝试为它们寻找新的节点。 您很可能很快就会用完集群中的新节点。 Kubernetes 集群的工作速度将开始变得越来越慢。

但我决定走得更远。 如您所知,在 Kubernetes 中有一种叫做服务的东西。 嗯,默认情况下,在您的集群中,该服务很可能使用 IP 表来工作。

例如,如果您运行 XNUMX 亿个 pod,然后使用脚本强制 Kubernetes 创建新服务:

for i in {1..1111111}; do
    kubectl expose deployment test --port 80  
        --overrides="{"apiVersion": "v1", 
           "metadata": {"name": "nginx$i"}}"; 
done 

在集群的所有节点上,越来越多的新 iptables 规则将几乎同时生成。 而且,每个服务都会生成XNUMX亿条iptables规则。

我检查了几千个,最多十个。 问题是,已经达到这个阈值,对节点进行 ssh 就很成问题了。 因为数据包经过如此多的链条,开始感觉不太好。

而这一切也都是在 Kubernetes 的帮助下解决的。 有这样一个资源配额对象。 设置集群中命名空间的可用资源和对象的数量。 我们可以在 Kubernetes 集群的每个命名空间中创建一个 yaml 对象。 使用这个对象,我们可以说我们为这个命名空间分配了一定数量的请求和限制,然后我们可以说在这个命名空间中可以创建 10 个服务和 10 个 Pod。 一个开发人员至少在晚上会窒息。 Kubernetes 会告诉他:“你无法将 Pod 扩展到这个数量,因为资源超出了配额。” 就这样,问题解决了。 文档在这里.

在这方面出现了一个问题。 你会感觉到在 Kubernetes 中创建命名空间变得多么困难。 为了创建它,我们需要考虑很多事情。

资源配额+限制范围+RBAC
• 创建命名空间
• 在内部创建一个限制范围
• 创建内部资源配额
• 为 CI 创建服务帐户
• 为 CI 和用户创建角色绑定
• 有选择地启动必要的服务容器

因此,我想借此机会分享一下我的进展。 有一个东西叫SDK操作符。 这是 Kubernetes 集群为其编写算子的一种方式。 您可以使用 Ansible 编写语句。

一开始是用Ansible写的,后来看到有SDK算子,就把Ansible角色重写成了算子。 该语句允许您在 Kubernetes 集群中创建一个称为命令的对象。 在命令内部,它允许您在 yaml 中描述该命令的环境。 在团队环境中,它允许我们描述我们正在分配如此多的资源。

娇小 使整个复杂的过程变得更容易.

并得出结论。 这一切该怎么办?
第一的。 Pod 安全策略很好。 尽管事实上到目前为止还没有 Kubernetes 安装程序使用它们,但您仍然需要在集群中使用它们。

网络策略不仅仅是另一个不必要的功能。 这才是集群真正需要的。

LimitRange/ResourceQuota - 是时候使用它了。 我们很久以前就开始使用它,很长一段时间我确信每个人都在使用它。 事实证明,这种情况很少见。

除了我在报告中提到的内容之外,还有一些未记录的功能可以让您攻击集群。 最近发布 对 Kubernetes 漏洞的广泛分析.

有些事情是那么的悲伤和伤人。 例如,在某些情况下,Kubernetes 集群中的 Cubelet 可以将 warlocks 目录的内容提供给未经授权的用户。

这里 有关于如何重现我告诉你的一切的说明。 有一些文件包含 ResourceQuota 和 Pod 安全策略的生产示例。 你可以触摸这一切。

谢谢大家。

来源: habr.com

添加评论