Kubernetes调度与安全设计
2024年1月23日 - 作者:Francesco Lacerenza, Lorenzo Stella
在测试活动中,我们通常分析设计选择和上下文需求,以便根据不同的Kubernetes部署模式建议适用的修复措施。调度在Kubernetes设计中经常被忽视。通常,各种机制会优先考虑,包括但不限于准入控制器、网络策略和RBAC配置。
然而,一个被入侵的Pod可能允许攻击者横向移动到运行在同一Kubernetes节点上的其他租户。Pod逃逸技术或共享存储系统可能被利用来实现跨租户访问,尽管有其他安全措施。
拥有面向安全的调度策略可以帮助在全面的安全设计中减少工作负载被入侵的整体风险。如果在调度决策中分离关键工作负载,被入侵Pod的爆炸半径就会减小。这样做可以防止与共享节点相关的横向移动,从低风险任务到业务关键工作负载。
攻击者在被入侵的Pod上,周围什么都没有
Kubernetes提供了多种机制来实现面向隔离的设计,如节点污点或亲和性。下面,我们描述Kubernetes提供的调度机制,并强调它们如何有助于可操作的风险降低。
将讨论以下应用调度策略的方法:
- nodeSelector字段匹配节点标签;
- nodeName和namespace字段,基本且有效;
- 亲和性和反亲和性,约束类型扩展用于包含和排斥;
- Pod间亲和性和反亲和性,在处理包含和排斥时,将标签匹配焦点放在Pod标签而不是节点标签上;
- 污点和容忍度,允许节点排斥或容忍Pod被调度;
- Pod拓扑分布约束,基于区域、区域、节点和其他用户定义的拓扑域;
- 设计自定义调度器,根据您的安全需求定制
工作负载分离机制
如前所述,将租户工作负载彼此隔离有助于减少被入侵邻居的影响。这是因为运行在某个节点上的所有Pod将属于单个租户。因此,能够从容器逃逸的攻击者只能访问该节点上挂载的容器和卷。
此外,具有不同授权的多个应用程序可能导致特权Pod与挂载了PII数据或具有不同安全风险级别的Pod共享节点。
1. nodeSelector
在约束中,这是最简单的,只需在Pod规范中指定目标节点标签。
示例Pod规范:
|
|
如果指定了多个标签,它们被视为必需(AND逻辑),因此调度只会在尊重所有标签的Pod上进行。
虽然在低复杂性环境中非常有用,但如果指定了许多选择器且节点不满足,它很容易成为停止执行的瓶颈。因此,如果需要应用许多约束,需要对分配给节点的标签进行良好的监控和动态管理。
2. nodeName
如果在Spec中设置了nodeName字段,kube调度器只需将Pod传递给kubelet,然后kubelet尝试将Pod分配给指定的节点。
在这种意义上,nodeName覆盖了其他调度规则(例如,nodeSelector、亲和性、反亲和性等),因为调度决策是预定义的。
示例Pod规范:
|
|
限制:
- 如果规范中的节点未运行或资源不足无法托管,Pod将不会运行
- 像AWS的EKS这样的云环境带有不可预测的节点名称
因此,需要对可用节点和分配给每组工作负载的资源进行详细管理,因为调度是预定义的。
注意:实际上,这种方法使调度器的所有计算效率优势无效,应仅应用于易于管理的小组关键工作负载。
3. 亲和性与反亲和性
NodeAffinity功能使得可以根据节点的某些特征或标签指定Pod调度规则。它们可用于确保Pod被调度到满足特定要求的节点上(亲和性规则),或避免将Pod调度到特定环境中(反亲和性规则)。
亲和性和反亲和性规则可以设置为“首选”(软)或“必需”(硬):
- 如果设置为preferredDuringSchedulingIgnoredDuringExecution,这表示软规则。调度器将尝试遵守此规则,但可能不总是这样做,特别是如果遵守规则会使调度不可能或具有挑战性。
- 如果设置为requiredDuringSchedulingIgnoredDuringExecution,则是硬规则。调度器不会调度Pod,除非满足条件。如果条件不满足,这可能导致Pod保持未调度(挂起)状态。
特别是,反亲和性规则可用于保护关键工作负载不与非关键工作负载共享kubelet。这样做,计算优化的缺乏不会影响整个节点池,而只是影响一些包含业务关键单元的实例。
节点亲和性示例:
|
|
节点首选位于特定网络段(通过标签),并且需要匹配p0或p1 workloadtype(自定义策略)。
多个操作符可用,NotIn和DoesNotExist是可用于获得节点反亲和性的特定操作符。从安全角度来看,只有需要遵守条件的硬规则才重要。preferredDuringSchedulingIgnoredDuringExecution配置应用于不影响集群安全态势的计算配置。
4. Pod间亲和性与反亲和性
Pod间亲和性和反亲和性可以约束Pod可以被调度到哪些节点上,基于已经运行在该节点上的Pod的标签。如Kubernetes文档中所述:
“Pod间亲和性和反亲和性规则的形式是‘这个Pod应该(或者,在反亲和性的情况下,不应该)运行在X中,如果X已经运行一个或多个满足规则Y的Pod’,其中X是拓扑域,如节点、机架、云提供商区域或区域,或类似,Y是Kubernetes尝试满足的规则。”
反亲和性示例:
|
|
在上述podAntiAffinity案例中,我们将永远不会看到Pod运行在运行testdatabase应用程序的节点上。
它适用于希望将一些Pod调度在一起或系统必须确保某些Pod永远不会被调度在一起的设计。特别是,Pod间规则允许工程师在同一执行上下文中定义额外的约束,而无需进一步创建节点组的分段。然而,复杂的亲和性规则可能创建Pod卡在挂起状态的情况。
5. 污点与容忍度
污点是节点亲和性属性的反面,因为它们允许节点排斥一组不匹配某些容忍度的Pod。它们可以应用于节点,使其排斥Pod,除非它们明确容忍污点。
容忍度应用于Pod,它们允许调度器调度具有匹配污点的Pod。应该强调,虽然容忍度允许调度,但决策不是保证的。
每个节点还定义了与每个污点链接的动作:NoExecute(影响运行中的Pod)、NoSchedule(硬规则)、PreferNoSchedule(软规则)。
这种方法适用于需要强工作负载隔离的环境。此外,它允许创建不基于标签的自定义节点选择规则,并且不留下灵活性。
6. Pod拓扑分布约束
您可以使用拓扑分布约束来控制Pod在故障域(如区域、区域、节点和其他用户定义的拓扑域)中如何分布。这有助于实现高可用性以及高效的资源利用。
7. 不满意?自定义调度器来救援
Kubernetes默认使用kube-scheduler,它遵循自己的一套Pod调度标准。虽然默认调度器功能多样且提供许多选项,但可能有特定的安全要求默认调度器可能不知道。编写自定义调度器允许组织应用基于风险的调度,以避免将特权Pod与处理或访问敏感数据的Pod配对。
要创建自定义调度器,您通常会编写一个程序:
- 监视未调度的Pod
- 实现调度算法以决定Pod应在哪个节点上运行
- 将决策传达给Kubernetes API服务器
一些可以适应此目的的自定义调度器示例可以在以下GH存储库中找到:kubernetes-sigs/scheduler-plugins或onuryilmaz/k8s-scheduler-example。
此外,关于构建自己的良好演示是Building a Kubernetes Scheduler using Custom Metrics - Mateo Burillo, Sysdig。如演讲中所述,这不是胆小者的事,因为复杂性,如果您不计划构建一个,最好坚持使用默认的。
攻击提示:调度策略像磁铁
如所述,调度策略可用于吸引或排斥Pod到特定的节点组。
虽然适当的策略减少了被入侵Pod的爆炸半径,但从攻击者的角度来看,仍有一些方面需要注意。
在特定情况下,实现的机制可能被用于:
- 吸引关键Pod - 能够编辑元数据的被入侵节点或角色可能被滥用来吸引攻击者感兴趣的Pod,通过操纵受控节点的标签。
仔细审查可能被滥用来编辑元数据的角色和内部流程。验证内部威胁通过影响或更改标签和污点来利用吸引的可能性
- 避免在关键节点上被拒绝 - 如果用户应该提交Pod规范或对它们如何动态构建有间接控制,这可能被滥用于调度部分。能够提交Pod规范的攻击者可能使用调度偏好跳转到托管关键工作负载的节点。
始终审查调度策略以找到允许Pod降落在托管关键工作负载的节点上的选项。验证用户控制的流程是否允许添加它们,或者逻辑是否可能被某些内部流程滥用
- 防止其他工作负载被调度 - 在某些情况下,了解或反转应用的策略可能允许特权攻击者制作Pod以在调度决策时阻止合法工作负载。
寻找可用于锁定节点上调度的潜在标签组合
奖励部分:节点标签安全
通常,kubelet仍然能够修改节点的标签,可能允许被入侵节点篡改自己的标签以欺骗调度器,如上所述。
可以使用NodeRestriction准入插件应用安全措施。它基本上拒绝来自kubelet的标签编辑,如果标签中存在node-restriction.kubernetes.io/前缀。
总结:是时候做出调度决策了
从安全角度来看,为每个命名空间/服务专用节点将构成最佳设置。然而,设计不会利用Kubernetes优化计算的能力。
以下示例代表了一些权衡选择:
- 在自己的节点组上隔离关键命名空间/工作负载
- 为每个命名空间的关键Pod保留一个节点
- 为关键命名空间部署完全独立的集群
成功方法的核心概念是拥有一组为关键命名空间/工作负载保留的节点。现实世界场景和复杂设计要求工程师根据性能要求和风险容忍度规划合适的机制组合。
这个决策始于定义工作负载的风险:
-
不同的团队,不同的信任级别 大型组织有多个团队部署到同一集群并不罕见。不同的团队可能有不同水平的可信度、培训或访问。这种多样性可能引入不同水平的风险。
-
正在处理或存储的数据 一些Pod可能需要挂载客户数据或具有持久秘密来执行任务。与任何工作负载较少的硬化工作负载共享节点可能将数据暴露于风险
-
在同一节点上暴露的网络服务 任何暴露网络服务的Pod都会增加其攻击面。与外部面向请求交互的Pod可能因此暴露而更易被入侵。
-
Pod特权和能力,或其分配的风险 一些工作负载可能需要一些特权来工作,或者可能运行本质上处理潜在不安全内容或第三方供应商代码的代码。所有这些因素都可能增加工作负载的分配风险。
一旦发现环境中的风险集,决定团队/数据/网络流量/能力的隔离级别。如果它们是同一过程的一部分,将它们分组可能解决问题。
此时,每个隔离组中的工作负载量应该是可评估的,并准备通过混合调度策略来解决,根据每个组的大小和复杂性。
注意:简单环境应使用简单策略,如果存在少量隔离组和约束,避免混合太多机制。
其他相关帖子:
-
Introducing PoIEx - Points Of Intersection Explorer 2024年1月30日
-
Testing Zero Touch Production Platforms and Safe Proxies 2023年5月4日