Kubernetes调度与安全设计
在测试活动中,我们通常分析设计选择和上下文需求,以便根据不同的Kubernetes部署模式建议适用的修复措施。调度在Kubernetes设计中经常被忽视。通常,各种机制会优先考虑,包括但不限于准入控制器、网络策略和RBAC配置。
然而,一个被入侵的pod可能允许攻击者横向移动到同一Kubernetes节点上运行的其他租户。尽管有其他安全措施,但pod逃逸技术或共享存储系统可能被利用来实现跨租户访问。
拥有面向安全的调度策略有助于在全面的安全设计中降低工作负载被入侵的整体风险。如果在调度决策中将关键工作负载分离,被入侵pod的爆炸半径就会减小。通过这样做,可以防止与共享节点相关的横向移动,从低风险任务到业务关键工作负载。
攻击者在被入侵的pod上,周围空无一物
Kubernetes提供了多种机制来实现面向隔离的设计,如节点污点或亲和性。下面,我们描述Kubernetes提供的调度机制,并强调它们如何促成可操作的风险降低。
将讨论以下应用调度策略的方法:
- 匹配节点标签的nodeSelector字段
- nodeName和namespace字段,基本且有效
- 亲和性和反亲和性,用于包含和排斥的约束类型扩展
- Pod间亲和性和反亲和性,在处理包含和排斥时,将标签匹配集中在pod标签而不是节点标签上
- 污点和容忍度,允许节点排斥或容忍pod被调度
- pod拓扑分布约束,基于区域、区域、节点和其他用户定义的拓扑域
- 设计自定义调度程序, tailored to your security needs
工作负载分离机制
如前所述,将租户工作负载彼此隔离有助于减少被入侵邻居的影响。这是因为在某个节点上运行的所有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(反亲和性规则)。
亲和性和反亲和性规则可以设置为"preferred"(软)或"required"(硬):
- 如果设置为preferredDuringSchedulingIgnoredDuringExecution,这表示软规则。调度程序将尝试遵守此规则,但可能不总是这样做,特别是在遵守规则会使调度不可能或具有挑战性时。
- 如果设置为requiredDuringSchedulingIgnoredDuringExecution,则为硬规则。除非满足条件,否则调度程序不会调度pod。如果条件不满足,这可能导致pod保持未调度(pending)状态。
特别是,可以利用反亲和性规则来保护关键工作负载不与非关键工作负载共享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卡在pending状态的情况。
5. 污点和容忍度
污点与节点亲和性属性相反,因为它们允许节点排斥一组不匹配某些容忍度的pod。它们可以应用于节点,使其排斥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特权和能力,或其分配的风险 一些工作负载可能需要一些特权才能工作,或者可能运行本质上处理潜在不安全内容或第三方供应商代码的代码。所有这些因素都可能增加工作负载的分配风险。
一旦找到环境中的风险集,决定团队/数据/网络流量/能力的隔离级别。如果它们是同一过程的一部分,将它们分组可能会起作用。
此时,每个隔离组中的工作负载量应该是可评估的,并准备通过混合调度策略来解决,根据每个组的大小和复杂性。
注意: 简单环境应使用简单策略,如果存在少量隔离组和约束,避免混合过多机制。