Kubernetes-安全学习手册(全)

Kubernetes 安全学习手册(全)

原文:zh.annas-archive.org/md5/389AEFE03E8149C2BB9C34B66276B16C

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

现实世界应用程序的不断复杂性和可扩展性已经导致了从单体架构向微服务架构的过渡。Kubernetes 已成为部署微服务的事实编排平台。作为一个开发者友好的平台,Kubernetes 可以启用不同的配置以适应不同的用例,使其成为大多数 DevOps 工程师的首选。Kubernetes 的开放性和高度可配置的特性增加了其复杂性。增加的复杂性会导致配置错误和安全问题,如果被利用,可能会对组织造成重大经济影响。如果您计划在您的环境中使用 Kubernetes,那么这本书就是为您准备的。

在这本书中,您将学习如何保护您的 Kubernetes 集群。我们在前两章中简要介绍了 Kubernetes(我们希望您在开始之前已经对 Kubernetes 有基本的了解)。然后,我们讨论了不同 Kubernetes 组件和对象的默认配置。Kubernetes 中的默认配置通常是不安全的。我们讨论了不同的方法来正确配置您的集群,以确保其安全。我们深入探讨了 Kubernetes 提供的不同内置安全机制,如准入控制器、安全上下文和网络策略,以帮助保护您的集群。我们还讨论了一些开源工具,这些工具可以补充 Kubernetes 中现有的工具包,以提高您的集群的安全性。最后,我们将看一些 Kubernetes 集群中的真实攻击和漏洞的例子,并讨论如何加固您的集群以防止此类攻击。

通过这本书,我们希望您能够在您的 Kubernetes 集群中安全地部署复杂的应用程序。Kubernetes 正在快速发展。通过我们提供的示例,我们希望您能学会如何为您的环境合理配置。

这本书适合谁

这本书适用于已经开始将 Kubernetes 作为他们主要的部署/编排平台并且对 Kubernetes 有基本了解的 DevOps/DevSecOps 专业人士。这本书也适用于希望学习如何保护和加固 Kubernetes 集群的开发人员。

这本书涵盖了什么

第一章, Kubernetes 架构,介绍了 Kubernetes 组件和 Kubernetes 对象的基础知识。

【第二章】介绍了 Kubernetes 的网络模型,并深入探讨了微服务之间的通信。

【第三章】讨论了 Kubernetes 中的重要资产、威胁者以及如何为部署在 Kubernetes 中的应用程序进行威胁建模。

【第四章】讨论了 Kubernetes 中的安全控制机制,帮助在两个领域实施最小特权原则:Kubernetes 主体的最小特权和 Kubernetes 工作负载的最小特权。

【第五章】讨论了 Kubernetes 集群中的安全域和安全边界。还介绍了加强安全边界的安全控制机制。

【第六章】讨论了 Kubernetes 组件中的敏感配置,如kube-apiserverkubelet等。介绍了使用kube-bench来帮助识别 Kubernetes 集群中的配置错误。

【第七章】讨论了 Kubernetes 中的认证和授权机制。还介绍了 Kubernetes 中流行的准入控制器。

【第八章】讨论了使用 CIS Docker 基准来加固图像。介绍了 Kubernetes 安全上下文、Pod 安全策略和kube-psp-advisor,它有助于生成 Pod 安全策略。

【第九章】介绍了 DevOps 流水线中的图像扫描的基本概念和容器图像以及漏洞。还介绍了图像扫描工具 Anchore Engine 以及如何将其集成到 DevOps 流水线中。

第十章, Kubernetes 集群的实时监控和资源管理,介绍了资源请求/限制和 LimitRanger 等内置机制。它还介绍了 Kubernetes 仪表板和指标服务器等内置工具,以及 Prometheus 和名为 Grafana 的第三方监控工具。

第十一章, 深度防御,讨论了与深度防御相关的各种主题:Kubernetes 审计、Kubernetes 的高可用性、密钥管理、异常检测和取证。

第十二章, 分析和检测加密货币挖矿攻击,介绍了加密货币和加密货币挖矿攻击的基本概念。然后讨论了使用 Prometheus 和 Falco 等开源工具检测加密货币挖矿攻击的几种方法。

第十三章, 从 Kubernetes CVE 中学习,讨论了四个众所周知的 Kubernetes CVE 以及一些相应的缓解策略。它还介绍了开源工具kube-hunter,帮助识别 Kubernetes 中已知的漏洞。

要充分利用本书

在开始阅读本书之前,我们希望您对 Kubernetes 有基本的了解。在阅读本书时,我们希望您以安全的心态看待 Kubernetes。本书有很多关于加固和保护 Kubernetes 工作负载配置和组件的示例。除了尝试这些示例之外,您还应该思考这些示例如何映射到不同的用例。我们在本书中讨论了如何使用不同的开源工具。我们希望您花更多时间了解每个工具提供的功能。深入了解工具提供的不同功能将帮助您了解如何为不同的环境配置每个工具:

如果您使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做将有助于避免与复制/粘贴代码相关的潜在错误。

请下载示例代码文件

您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packtpub.com/support并注册,文件将直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. www.packt.com上登录或注册。

  2. 选择支持选项卡。

  3. 点击代码下载

  4. 搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用最新版本的解压软件解压文件夹:

  • WinRAR/7-Zip 适用于 Windows

  • Zipeg/iZip/UnRarX 适用于 Mac

  • 7-Zip/PeaZip 适用于 Linux

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Learn-Kubernetes-Security。如果代码有更新,将在现有的 GitHub 存储库中进行更新。

我们还有其他代码包,来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/上找到。去看看吧!

代码实例

本书的代码实例视频可在bit.ly/2YZKCJX上观看。

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781839216503_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

文本中的代码:表示文本中的代码字词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:"此属性也在PodSecurityContext中可用,它在 Pod 级别生效。"

代码块设置如下:

{
  "filename": "/tmp/minerd2",
  "gid": 0,
  "linkdest": null,
}

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

{
  "scans": {
    "Fortinet": {
      "detected": true,
    }
  }

任何命令行输入或输出都以以下方式编写:

$ kubectl get pods -n insecure-nginx

粗体:表示新术语、重要词汇或屏幕上看到的词语。例如,菜单或对话框中的词语在文本中显示为这样。例如:"屏幕截图显示了由 Prometheus 和 Grafana 监控的insecure-nginx pod 的 CPU 使用情况。"

提示或重要说明

就像这样。

第一部分:Kubernetes 简介

在本节中,您将掌握 Kubernetes 架构、网络模型、威胁模型以及应用于 Kubernetes 集群的核心安全原则的基本概念。

本节包括以下章节:

  • 第一章,Kubernetes 架构

  • 第二章,Kubernetes 网络

  • 第三章,威胁建模

  • 第四章,在 Kubernetes 中应用最小权限原则

  • 第五章,配置 Kubernetes 安全边界

第一章:Kubernetes 架构

传统应用程序,如 Web 应用程序,通常遵循模块化架构,将代码分为应用层、业务逻辑、存储层和通信层。尽管采用了模块化架构,但组件被打包并部署为单体。单体应用虽然易于开发、测试和部署,但难以维护和扩展。这导致了微服务架构的增长。像 Docker 和 Linux 容器(LXC)这样的容器运行时的开发已经简化了应用程序作为微服务的部署和维护。

微服务架构将应用部署分为小型且相互连接的实体。微服务架构的日益流行导致了诸如 Apache Swarm、Mesos 和 Kubernetes 等编排平台的增长。容器编排平台有助于在大型和动态环境中管理容器。

Kubernetes 是一个开源的容器化应用编排平台,支持自动化部署、扩展和管理。它最初由 Google 在 2014 年开发,现在由云原生计算基金会(CNCF)维护。Kubernetes 是 2018 年首个毕业于 CNCF 的项目。成立的全球组织,如 Uber、Bloomberg、Blackrock、BlaBlaCar、纽约时报、Lyft、eBay、Buffer、Ancestry、GolfNow、高盛等,都在大规模生产中使用 Kubernetes。大型云服务提供商,如 Amazon 的弹性 Kubernetes 服务、微软的 Azure Kubernetes 服务、谷歌的谷歌 Kubernetes 引擎和阿里巴巴的阿里云 Kubernetes,都提供自己的托管 Kubernetes 服务。

在微服务模型中,应用程序开发人员确保应用程序在容器化环境中正常工作。他们编写 Docker 文件来打包他们的应用程序。DevOps 和基础设施工程师直接与 Kubernetes 集群进行交互。他们确保开发人员提供的应用程序包在集群中顺利运行。他们监视节点、Pod 和其他 Kubernetes 组件,以确保集群健康。然而,安全性需要双方和安全团队的共同努力。要了解如何保护 Kubernetes 集群,我们首先必须了解 Kubernetes 是什么以及它是如何工作的。

在本章中,我们将涵盖以下主题:

  • Docker 的崛起和微服务的趋势

  • Kubernetes 组件

  • Kubernetes 对象

  • Kubernetes 的变种

  • Kubernetes 和云服务提供商

Docker 的崛起和微服务的趋势

在我们开始研究 Kubernetes 之前,了解微服务和容器化的增长是很重要的。随着单体应用程序的演变,开发人员面临着不可避免的问题:

  • 扩展:单体应用程序很难扩展。已经证明解决可扩展性问题的正确方法是通过分布式方法。

  • 运营成本:随着单体应用程序的复杂性增加,运营成本也会增加。更新和维护需要在部署之前进行仔细分析和足够的测试。这与可扩展性相反;你不能轻易地缩减单体应用程序,因为最低资源需求很高。

  • 发布周期更长:对于单体应用程序,维护和开发的障碍非常高。对于开发人员来说,当出现错误时,在复杂且不断增长的代码库中识别根本原因需要很长时间。测试时间显著增加。回归、集成和单元测试在复杂的代码库中需要更长的时间才能通过。当客户的请求到来时,一个功能要发布需要几个月甚至一年的时间。这使得发布周期变长,并且对公司的业务产生重大影响。

这激励着将单片应用程序拆分为微服务。好处是显而易见的:

  • 有了明确定义的接口,开发人员只需要关注他们拥有的服务的功能。

  • 代码逻辑被简化了,这使得应用程序更容易维护和调试。此外,与单片应用程序相比,微服务的发布周期大大缩短,因此客户不必等待太长时间才能获得新功能。

当单片应用程序分解为许多微服务时,这增加了 DevOps 方面的部署和管理复杂性。这种复杂性是显而易见的;微服务通常使用不同的编程语言编写,需要不同的运行时或解释器,具有不同的软件包依赖关系、不同的配置等,更不用说微服务之间的相互依赖了。这正是 Docker 出现的合适时机。

让我们来看一下 Docker 的演变。进程隔离长期以来一直是 Linux 的一部分,以控制组cgroups)和命名空间的形式存在。通过 cgroup 设置,每个进程都有限制的资源(CPU、内存等)可供使用。通过专用的进程命名空间,命名空间内的进程不会知道在同一节点但在不同进程命名空间中运行的其他进程。通过专用的网络命名空间,进程在没有适当的网络配置的情况下无法与其他进程通信,即使它们在同一节点上运行。

Docker 简化了基础设施和 DevOps 工程师的进程管理。2013 年,Docker 公司发布了 Docker 开源项目。DevOps 工程师不再需要管理命名空间和 cgroups,而是通过 Docker 引擎管理容器。Docker 容器利用 Linux 中的这些隔离机制来运行和管理微服务。每个容器都有专用的 cgroup 和命名空间。

相互依赖的复杂性仍然存在。编排平台是试图解决这个问题的平台。Docker 还提供了 Docker Swarm 模式(后来更名为 Docker 企业版,或 Docker EE)来支持集群容器,与 Kubernetes 大致同时期。

Kubernetes 采用状态

根据 Sysdig 在 2019 年进行的容器使用报告(sysdig.com/blog/sysdig-2019-container-usage-report),一家容器安全和编排供应商表示,Kubernetes 在使用的编排器中占据了惊人的 77%的份额。如果包括 OpenShift(来自 Red Hat 的 Kubernetes 变体),市场份额接近 90%:

图 1.1 –编排平台的市场份额

图 1.1 –编排平台的市场份额

尽管 Docker Swarm 与 Kubernetes 同时发布,但 Kubernetes 现在已成为容器编排平台的事实选择。这是因为 Kubernetes 能够在生产环境中很好地工作。它易于使用,支持多种开发人员配置,并且可以处理高规模环境。

Kubernetes 集群

Kubernetes 集群由多台机器(或虚拟机VMs))或节点组成。有两种类型的节点:主节点和工作节点。主控制平面,如kube-apiserver,运行在主节点上。每个工作节点上运行的代理称为kubelet,代表kube-apiserver运行,并运行在工作节点上。Kubernetes 中的典型工作流程始于用户(例如,DevOps),与主节点中的kube-apiserver通信,kube-apiserver将部署工作委派给工作节点。在下一节中,我们将更详细地介绍kube-apiserverkubelet

图 1.2 – Kubernetes 部署

图 1.2 – Kubernetes 部署

上图显示用户如何向主节点(kube-apiserver)发送部署请求,kube-apiserver将部署执行委派给一些工作节点中的kubelet

Kubernetes 组件

Kubernetes 遵循客户端-服务器架构。在 Kubernetes 中,多个主节点控制多个工作节点。每个主节点和工作节点都有一组组件,这些组件对于集群的正常工作是必需的。主节点通常具有kube-apiserveretcd存储、kube-controller-managercloud-controller-managerkube-scheduler。工作节点具有kubeletkube-proxy容器运行时接口(CRI)组件、容器存储接口(CRI)组件等。我们现在将详细介绍每一个:

  • kube-apiserver:Kubernetes API 服务器(kube-apiserver)是一个控制平面组件,用于验证和配置诸如 pod、服务和控制器等对象的数据。它使用 REST 请求与对象交互。

  • etcdetcd是一个高可用的键值存储,用于存储配置、状态和元数据等数据。etcd的 watch 功能使 Kubernetes 能够监听配置的更新并相应地进行更改。

  • kube-schedulerkube-scheduler是 Kubernetes 的默认调度程序。它监视新创建的 pod 并将 pod 分配给节点。调度程序首先过滤可以运行 pod 的一组节点。过滤包括根据用户设置的可用资源和策略创建可能节点的列表。一旦创建了这个列表,调度程序就会对节点进行排名,找到最适合 pod 的节点。

  • kube-controller-manager:Kubernetes 控制器管理器是一组核心控制器,它们监视状态更新并相应地对集群进行更改。目前随 Kubernetes 一起提供的控制器包括以下内容:

  • cloud-controller-manager:云容器管理器是在 v1.6 中引入的,它运行控制器与底层云提供商进行交互。这是为了将云供应商的代码与 Kubernetes 的代码解耦。

  • kubeletkubelet在每个节点上运行。它向 API 服务器注册节点。kubelet监视使用 Podspecs 创建的 pod,并确保 pod 和容器健康。

  • kube-proxykube-proxy是在每个节点上运行的网络代理。它管理每个节点上的网络规则,并根据这些规则转发或过滤流量。

  • kube-dns:DNS 是在集群启动时启动的内置服务。从 v1.12 开始,CoreDNS 成为推荐的 DNS 服务器,取代了kube-dns。CoreDNS 使用单个容器(而不是kube-dns使用的三个容器)。它使用多线程缓存,并具有内置的负缓存,因此在内存和性能方面优于kube-dns

在本节中,我们看了 Kubernetes 的核心组件。这些组件将存在于所有的 Kubernetes 集群中。Kubernetes 还有一些可配置的接口,允许对集群进行修改以适应组织的需求。

Kubernetes 接口

Kubernetes 旨在灵活和模块化,因此集群管理员可以修改网络、存储和容器运行时能力,以满足组织的需求。目前,Kubernetes 提供了三种不同的接口,集群管理员可以使用这些接口来使用集群中的不同功能。

容器网络接口

Kubernetes 有一个默认的网络提供程序 kubenet,其功能有限。kubenet 只支持每个集群 50 个节点,显然无法满足大规模部署的任何要求。同时,Kubernetes 利用容器网络接口CNI)作为网络提供程序和 Kubernetes 网络组件之间的通用接口,以支持大规模集群中的网络通信。目前支持的提供程序包括 Calico、Flannel、kube-router 等。

容器存储接口

Kubernetes 在 v1.13 中引入了容器存储接口。在 1.13 之前,新的卷插件是核心 Kubernetes 代码的一部分。容器存储接口提供了一个接口,用于向 Kubernetes 公开任意块和文件存储。云提供商可以使用 CSI 插件向 Kubernetes 公开高级文件系统。MapR 和 Snapshot 等插件在集群管理员中很受欢迎。

容器运行时接口

在 Kubernetes 的最低级别,容器运行时确保容器启动、工作和停止。最流行的容器运行时是 Docker。容器运行时接口使集群管理员能够使用其他容器运行时,如 fraktirktletcri-o

Kubernetes 对象

系统的存储和计算资源被分类为反映集群当前状态的不同对象。对象使用 .yaml 规范进行定义,并使用 Kubernetes API 来创建和管理这些对象。我们将详细介绍一些常见的 Kubernetes 对象。

Pods

Pod 是 Kubernetes 集群的基本构建块。它是一个或多个容器的组,这些容器预期在单个主机上共存。Pod 中的容器可以使用本地主机或进程间通信IPC)相互引用。

部署

Kubernetes 部署可以根据标签和选择器来扩展或缩减 pod。部署的 YAML 规范包括 replicas,即所需的 pod 实例数量,以及 template,与 pod 规范相同。

服务

Kubernetes 服务是应用程序的抽象。服务为 pod 提供网络访问。服务和部署共同工作,以便简化不同应用程序的不同 pod 之间的管理和通信。

副本集

副本集确保系统中始终运行指定数量的 pod。最好使用部署而不是副本集。部署封装了副本集和 pod。此外,部署提供了进行滚动更新的能力。

容器存储是暂时的。如果容器崩溃或重新启动,它会从启动时的原始状态开始。Kubernetes 卷有助于解决这个问题。容器可以使用卷来存储状态。Kubernetes 卷的生命周期与 pod 相同;一旦 pod 消失,卷也会被清理掉。一些支持的卷包括awsElasticBlockStoreazureDiskflockernfsgitRepo

命名空间

命名空间帮助将物理集群划分为多个虚拟集群。多个对象可以在不同的命名空间中进行隔离。默认的 Kubernetes 附带三个命名空间:defaultkube-systemkube-public

服务账户

需要与kube-apiserver交互的 pod 使用服务账户来标识自己。默认情况下,Kubernetes 配置了一系列默认服务账户:kube-proxykube-dnsnode-controller等。可以创建额外的服务账户来强制执行自定义访问控制。

网络策略

网络策略定义了一组规则,规定了一组 pod 如何允许与彼此和其他网络端点进行通信。所有传入和传出的网络连接都受网络策略的控制。默认情况下,一个 pod 可以与所有 pod 进行通信。

Pod 安全策略

Pod 安全策略是一个集群级资源,定义了必须满足的一组条件,才能在系统上运行 pod。Pod 安全策略定义了 pod 的安全敏感配置。这些策略必须对请求用户或目标 pod 的服务账户可访问才能生效。

Kubernetes 变体

在 Kubernetes 生态系统中,Kubernetes 是各种变体中的旗舰。然而,还有一些其他起着非常重要作用的船只。接下来,我们将介绍一些类似 Kubernetes 的平台,在生态系统中发挥不同的作用。

Minikube

Minikube 是 Kubernetes 的单节点集群版本,可以在 Linux、macOS 和 Windows 平台上运行。Minikube 支持标准的 Kubernetes 功能,如LoadBalancer、服务、PersistentVolumeIngress、容器运行时,以及开发人员友好的功能,如附加组件和 GPU 支持。

Minikube 是一个很好的起点,可以让您亲身体验 Kubernetes。它也是一个很好的地方来在本地运行测试,特别是集群依赖或工作在概念验证上。

K3s

K3s 是一个轻量级的 Kubernetes 平台。其总大小不到 40MB。它非常适合边缘计算,物联网(IoT)和 ARM,先前是高级 RISC 机器,最初是 Acorn RISC Machine 的一系列用于各种环境的精简指令集计算(RISC)架构的计算机处理器。它应该完全符合 Kubernetes。与 Kubernetes 的一个重要区别是,它使用sqlite作为默认存储机制,而 Kubernetes 使用etcd作为其默认存储服务器。

OpenShift

OpenShift 3 版本采用了 Docker 作为其容器技术,Kubernetes 作为其容器编排技术。在第 4 版中,OpenShift 切换到 CRI-O 作为默认的容器运行时。看起来 OpenShift 应该与 Kubernetes 相同;然而,它们之间有相当多的区别。

OpenShift 与 Kubernetes

Linux 和 Red Hat Linux 之间的联系可能首先看起来与 OpenShift 和 Kubernetes 之间的联系相同。现在,让我们来看一下它们的一些主要区别。

命名

在 Kubernetes 中命名的对象在 OpenShift 中可能有不同的名称,尽管有时它们的功能是相似的。例如,在 Kubernetes 中,命名空间称为 OpenShift 中的项目,并且项目创建附带默认对象。在 Kubernetes 中,Ingress 称为 OpenShift 中的路由。路由实际上比 Ingress 对象更早引入。在 OpenShift 下,路由由 HAProxy 实现,而在 Kubernetes 中有许多 Ingress 控制器选项。在 Kubernetes 中,部署称为deploymentConfig。然而,在底层实现上有很大的不同。

安全性

Kubernetes 默认是开放的,安全性较低。OpenShift 相对封闭,并提供了一些良好的安全机制来保护集群。例如,在创建 OpenShift 集群时,DevOps 可以启用内部镜像注册表,该注册表不会暴露给外部。同时,内部镜像注册表充当受信任的注册表,图像将从中拉取和部署。OpenShift 项目在某些方面比kubernetes命名空间做得更好——在 OpenShift 中创建项目时,可以修改项目模板并向项目添加额外的对象,例如NetworkPolicy和符合公司政策的默认配额。这也有助于默认加固。

成本

OpenShift 是 Red Hat 提供的产品,尽管有一个名为 OpenShift Origin 的社区版本项目。当人们谈论 OpenShift 时,他们通常指的是得到 Red Hat 支持的付费 OpenShift 产品。Kubernetes 是一个完全免费的开源项目。

Kubernetes 和云提供商

很多人相信 Kubernetes 是基础设施的未来,也有一些人相信一切都会最终转移到云上。然而,这并不意味着你必须在云上运行 Kubernetes,但它确实在云上运行得非常好。

Kubernetes 作为服务

容器化使应用程序更具可移植性,因此不太可能与特定的云提供商绑定。尽管有一些出色的开源工具,如kubeadmkops,可以帮助 DevOps 创建 Kubernetes 集群,但云提供商提供的 Kubernetes 作为服务仍然很有吸引力。作为 Kubernetes 的原始创建者,Google 自 2014 年起就提供了 Kubernetes 作为服务。它被称为Google Kubernetes EngineGKE)。2017 年,微软推出了自己的 Kubernetes 服务,称为Azure Kubernetes ServiceAKS)。AWS 在 2018 年推出了Elastic Kubernetes ServiceEKS)。

Kubedex(kubedex.com/google-gke-vs-microsoft-aks-vs-amazon-eks/)对云 Kubernetes 服务进行了很好的比较。以下表格列出了这三者之间的一些差异:

前面列表中值得强调的一些亮点如下:

  • 可扩展性:GKE 支持每个集群最多 5000 个节点,而 AKS 和 EKS 只支持少量节点或更少。

  • 高级安全选项:GKE 支持 Istio 服务网格、Sandbox、二进制授权和入口管理的安全套接字层SSL),而 AKS 和 EKS 则不支持。

如果计划在由云提供商提供的 Kubernetes 集群中部署和管理微服务,您需要考虑云提供商提供的可扩展性能力以及安全选项。如果您使用由云提供商管理的集群,则存在一些限制:

  • 云提供商默认情况下会执行一些集群配置和加固,并且可能无法更改。

  • 您失去了管理 Kubernetes 集群的灵活性。例如,如果您想要启用 Kubernetes 的审计策略并将审计日志导出到splunk,您可能需要对kube-apiserver清单进行一些配置更改。

  • 对运行kube-apiserver的主节点的访问受到限制。如果您专注于部署和管理微服务,这种限制完全是有意义的。在某些情况下,您需要启用一些准入控制器,然后还需要对kube-apiserver清单进行更改。这些操作需要访问主节点。

如果您想要访问集群节点的 Kubernetes 集群,可以使用一个开源工具——kops

Kops

Kubernetes 操作kops)有助于通过命令行创建、销毁、升级和维护高可用的生产级 Kubernetes 集群。它正式支持 AWS,并在 beta 版本中支持 GCE 和 OpenStack。与在云 Kubernetes 服务上提供 Kubernetes 集群的主要区别在于,提供是从 VM 层开始的。这意味着使用kops可以控制您想要使用的操作系统映像,并设置自己的管理员 SSH 密钥以访问主节点和工作节点。在 AWS 中创建 Kubernetes 集群的示例如下:

  # Create a cluster in AWS that has HA masters. This cluster
  # will be setup with an internal networking in a private VPC.
  # A bastion instance will be setup to provide instance access.
  export NODE_SIZE=${NODE_SIZE:-m4.large}
  export MASTER_SIZE=${MASTER_SIZE:-m4.large}
  export ZONES=${ZONES:-'us-east-1d,us-east-1b,us-east-1c'}
  export KOPS_STATE_STORE='s3://my-state-store'
  kops create cluster k8s-clusters.example.com \
  --node-count 3 \
  --zones $ZONES \
  --node-size $NODE_SIZE \
  --master-size $MASTER_SIZE \
  --master-zones $ZONES \
  --networking weave \
  --topology private \
  --bastion='true' \
  --yes

通过前面的kops命令,创建了一个包含三个工作节点的 Kubernetes 集群。用户可以选择主节点和 CNI 插件的大小。

为什么要担心 Kubernetes 的安全性?

Kubernetes 在 2018 年正式推出,并且仍在快速发展。还有一些功能仍在开发中,尚未达到 GA 状态(alpha 或 beta)。这表明 Kubernetes 本身远未成熟,至少从安全的角度来看。但这并不是我们需要关注 Kubernetes 安全性的主要原因。

Bruce Schneier 在 1999 年的一篇名为《简化的请求》的文章中最好地总结了这一点,他说“复杂性是安全的最大敌人”,准确预测了我们今天遇到的网络安全问题。为了满足稳定性、可扩展性、灵活性和安全性的所有主要编排需求,Kubernetes 被设计成复杂但紧密的方式。这种复杂性无疑带来了一些安全问题。

可配置性是 Kubernetes 平台对开发人员的主要优势之一。开发人员和云提供商可以自由配置他们的集群以满足他们的需求。Kubernetes 的这一特性是企业日益增加的安全担忧的主要原因之一。Kubernetes 代码的不断增长和 Kubernetes 集群的组件使得 DevOps 难以理解正确的配置。默认配置通常不安全(开放性确实为 DevOps 尝试新功能带来了优势)。

随着 Kubernetes 的使用增加,它因各种安全漏洞和缺陷而成为新闻头条:

  • Palo Alto Networks 的研究人员发现了 40,000 个 Docker 和 Kubernetes 容器暴露在互联网上。这是由于配置错误导致的结果。

  • 攻击者利用了特斯拉的未加密管理控制台来运行加密挖矿设备。

  • 在 Kubernetes 版本中发现了特权升级漏洞,允许经过精心设计的请求通过 API 服务器与后端建立连接并发送任意请求。

  • 在生产环境中使用 Kubernetes 元数据测试版功能导致了对流行的电子商务平台 Shopify 的服务器端请求伪造SSRF)攻击。这个漏洞暴露了 Kubernetes 元数据,揭示了 Google 服务帐户令牌和kube-env详细信息,使攻击者能够 compromise 集群。

The New Stack 最近的一项调查(thenewstack.io/top-challenges-kubernetes-users-face-deployment/)显示,安全是运行 Kubernetes 的企业的主要关注点:

图 1.3 - Kubernetes 用户的主要关注点

图 1.3 - Kubernetes 用户的主要关注点

Kubernetes 默认情况下不安全。我们将在后面的章节中详细解释这一点。安全成为用户的主要关注点之一是完全有道理的。这是一个需要妥善解决的问题,就像其他基础设施或平台一样。

摘要

微服务的趋势和 Docker 的兴起使得 Kubernetes 成为 DevOps 部署、扩展和管理容器化应用程序的事实标准平台。Kubernetes 将存储和计算资源抽象为 Kubernetes 对象,由kube-apiserverkubeletetcd等组件管理。

Kubernetes 可以在私有数据中心或云上或混合环境中创建。这使得 DevOps 可以与多个云提供商合作,而不会被锁定在任何一个云提供商上。尽管 Kubernetes 在 2018 年已经成熟,但它仍然年轻,并且发展非常迅速。随着 Kubernetes 受到越来越多的关注,针对 Kubernetes 的攻击也变得更加显著。

在下一章中,我们将介绍 Kubernetes 网络模型,并了解微服务在 Kubernetes 中如何相互通信。

问题

  1. 单体架构的主要问题是什么?

  2. Kubernetes 的主要组件是什么?

  3. 部署是什么?

  4. Kubernetes 的一些变体是什么?

  5. 我们为什么关心 Kubernetes 的安全性?

进一步阅读

以下链接包含有关 Kubernetes、kops和 OpenShift 平台的更详细信息。在开始构建 Kubernetes 集群时,您会发现它们很有用:

第二章:Kubernetes 网络

当成千上万的微服务在 Kubernetes 集群中运行时,您可能会好奇这些微服务如何相互通信以及与互联网通信。在本章中,我们将揭示 Kubernetes 集群中所有通信路径。我们希望您不仅了解通信是如何发生的,还要以安全意识查看技术细节:常规通信渠道总是可以作为 kill 链的一部分被滥用。

在本章中,我们将涵盖以下主题:

  • Kubernetes 网络模型概述

  • 在 pod 内部通信

  • 在 pod 之间通信

  • 引入 Kubernetes 服务

  • 引入 CNI 和 CNI 插件

Kubernetes 网络模型概述

在 Kubernetes 集群上运行的应用程序应该可以从集群内部或外部访问。从网络的角度来看,这意味着应用程序可能与统一资源标识符URI)或互联网协议IP)地址相关联。多个应用程序可以在同一 Kubernetes 工作节点上运行,但它们如何在不与彼此冲突的情况下暴露自己呢?让我们一起来看看这个问题,然后深入了解 Kubernetes 网络模型。

端口共享问题

传统上,如果在同一台机器上运行两个不同的应用程序,其中机器 IP 是公共的,并且这两个应用程序是公开访问的,那么这两个应用程序不能在机器上监听相同的端口。如果它们都尝试在同一台机器的相同端口上监听,由于端口被使用,一个应用程序将无法启动。下图提供了这个问题的简单说明:

图 2.1 - 节点上的端口共享冲突(应用程序)

图 2.1 - 节点上的端口共享冲突(应用程序)

为了解决端口共享冲突问题,这两个应用程序需要使用不同的端口。显然,这里的限制是这两个应用程序必须共享相同的 IP 地址。如果它们有自己的 IP 地址,但仍然位于同一台机器上会怎样?这就是纯 Docker 的方法。如果应用程序不需要外部暴露自己,这将有所帮助,如下图所示:

图 2.2 - 节点上的端口共享冲突(容器)

图 2.2 - 节点上的端口共享冲突(容器)

在上图中,两个应用程序都有自己的 IP 地址,因此它们都可以监听端口 80。它们可以相互通信,因为它们位于同一个子网中(例如,一个 Docker 桥接)。然而,如果两个应用程序都需要通过将容器端口绑定到主机端口来在外部公开自己,它们就不能绑定在相同的端口 80 上。至少一个端口绑定将失败。如上图所示,容器 B 无法绑定到主机端口 80,因为主机端口 80 被容器 A 占用。端口共享冲突问题仍然存在。

动态端口配置给系统带来了很多复杂性,涉及端口分配和应用程序发现;然而,Kubernetes 并不采取这种方法。让我们讨论一下 Kubernetes 解决这个问题的方法。

Kubernetes 网络模型

在 Kubernetes 集群中,每个 Pod 都有自己的 IP 地址。这意味着应用程序可以在 Pod 级别相互通信。这种设计的美妙之处在于,它提供了一个清晰、向后兼容的模型,其中 Pod 在端口分配、命名、服务发现、负载平衡、应用程序配置和迁移方面的表现就像虚拟机(VM)或物理主机一样。同一 Pod 内的容器共享相同的 IP 地址。很少有类似的应用程序会在同一 Pod 内使用相同的默认端口(如 Apache 和 nginx)。实际上,捆绑在同一容器内的应用程序通常具有依赖性或提供不同的目的,这取决于应用程序开发人员将它们捆绑在一起。一个简单的例子是,在同一个 Pod 中,有一个超文本传输协议(HTTP)服务器或一个 nginx 容器来提供静态文件,以及一个用于提供动态内容的主 Web 应用程序。

Kubernetes 利用 CNI 插件来实现 IP 地址分配、管理和 Pod 通信。然而,所有插件都需要遵循以下两个基本要求:

  1. 节点上的 Pod 可以与所有节点上的所有 Pod 进行通信,而无需使用网络地址转换(NAT)。

  2. 诸如kubelet之类的代理可以与同一节点上的 Pod 进行通信。

这两个先前的要求强制了在虚拟机内迁移应用程序到 Pod 的简单性。

分配给每个 pod 的 IP 地址是一个私有 IP 地址或集群 IP 地址,不对公众开放。那么,一个应用程序如何在不与集群中的其他应用程序发生冲突的情况下变得对公众可访问呢?Kubernetes 服务就是将内部应用程序暴露给公众的方式。我们将在后面的章节中更深入地探讨 Kubernetes 服务的概念。现在,用下面的图表总结本章内容将会很有用:

图 2.3 - 服务暴露给互联网

图 2.3 - 服务暴露给互联网

在上一个图表中,有一个k8s 集群,其中有四个应用程序在两个 pod 中运行:应用程序 A应用程序 BPod X中运行,并共享相同的 pod IP 地址—100.97.240.188—它们分别在端口80809090上监听。同样,应用程序 C应用程序 DPod Y中运行,并分别在端口80009000上监听。所有这四个应用程序都可以通过以下面向公众的 Kubernetes 服务进行访问:svc.a.comsvc.b.comsvc.c.comsvc.d.com。这个图表中的 pod(X 和 Y)可以部署在一个单独的工作节点上,也可以在 1000 个节点上复制。然而,从用户或服务的角度来看,这并没有什么区别。尽管图表中的部署方式相当不寻常,但仍然需要在同一个 pod 内部部署多个容器。现在是时候来看看同一个 pod 内部容器之间的通信了。

在 pod 内部通信

同一个 pod 内的容器共享相同的 pod IP 地址。通常,将容器映像捆绑在一起并解决可能的资源使用冲突(如端口监听)是应用程序开发人员的责任。在本节中,我们将深入探讨容器内部通信的技术细节,并强调超出网络层面的通信。

Linux 命名空间和暂停容器

Linux 命名空间是 Linux 内核的一个特性,用于分区资源以进行隔离。使用分配的命名空间,一组进程看到一组资源,而另一组进程看到另一组资源。命名空间是现代容器技术的一个重要基本方面。读者理解这个概念对于深入了解 Kubernetes 很重要。因此,我们列出了所有的 Linux 命名空间并进行了解释。自 Linux 内核版本 4.7 以来,有七种类型的命名空间,如下所示:

  • cgroup:隔离 cgroup 和根目录。cgroup 命名空间虚拟化了进程 cgroup 的视图。每个 cgroup 命名空间都有自己的 cgroup 根目录集。

  • IPC:隔离 System V 进程间通信(IPC)对象或 POSIX 消息队列。

  • 网络:隔离网络设备、协议栈、端口、IP 路由表、防火墙规则等。

  • 挂载:隔离挂载点。因此,每个挂载命名空间实例中的进程将看到不同的单目录层次结构。

  • PID:隔离进程 ID(PIDs)。不同 PID 命名空间中的进程可以具有相同的 PID。

  • 用户:隔离用户 ID 和组 ID、根目录、密钥和功能。在用户命名空间内外,进程可以具有不同的用户和组 ID。

  • Unix 时间共享(UTS):隔离两个系统标识符:主机名和网络信息服务(NIS)域名。

尽管每个命名空间都很强大,并且在不同资源上提供隔离目的,但并非所有命名空间都适用于同一 Pod 内的容器。同一 Pod 内的容器至少共享相同的 IPC 命名空间和网络命名空间;因此,K8s 需要解决端口使用可能的冲突。将创建一个回环接口,以及分配给 Pod 的 IP 地址的虚拟网络接口。更详细的图表将如下所示:

图 2.4 - Pod 内的容器

图 2.4 - Pod 内的容器

在这个图表中,有一个Pause容器与容器AB一起运行在同一个 pod 中。如果您通过Secure ShellSSH)进入 Kubernetes 集群节点并在节点内运行docker ps命令,您将看到至少一个使用pause命令启动的容器。pause命令会暂停当前进程,直到接收到信号。基本上,这些容器什么也不做,只是休眠。尽管没有活动,Pause容器在 pod 中扮演着关键的角色。它作为一个占位符,为同一个 pod 中的所有其他容器持有网络命名空间。与此同时,Pause容器获取了一个 IP 地址,用于所有其他容器之间以及与外部世界通信的虚拟网络接口。

超越网络通信

我们决定在同一个 pod 中的容器之间稍微超越网络通信。这样做的原因是通信路径有时可能成为杀伤链的一部分。因此,了解实体之间可能的通信方式非常重要。您将在第三章中看到更多相关内容,威胁建模

在一个 pod 内,所有容器共享相同的 IPC 命名空间,以便容器可以通过 IPC 对象或 POSIX 消息队列进行通信。除了 IPC 通道,同一个 pod 内的容器还可以通过共享挂载卷进行通信。挂载的卷可以是临时内存、主机文件系统或云存储。如果卷被 Pod 中的容器挂载,那么容器可以读写卷中的相同文件。最后但并非最不重要的是,在 1.12 Kubernetes 版本的 beta 版中,shareProcessNamespace功能最终在 1.17 版本中稳定。用户可以简单地在 Podspec 中设置shareProcessNamespace选项,以允许 pod 内的容器共享一个公共 PID 命名空间。其结果是Container A中的Application A现在能够看到Container B中的Application B。由于它们都在相同的 PID 命名空间中,它们可以使用诸如 SIGTERM、SIGKILL 等信号进行通信。这种通信可以在以下图表中看到:

图 2.5 - pod 内部容器之间可能的通信

图 2.5 - pod 内部容器之间可能的通信

As the previous diagram shows, containers inside the same pod can communicate to each other via a network, an IPC channel, a shared volume, and through signals.

Communicating between pods

Kubernetes pods are dynamic beings and ephemeral. When a set of pods is created from a deployment or a DaemonSet, each pod gets its own IP address; however, when patching happens or a pod dies and restarts, pods may have a new IP address assigned. This leads to two fundamental communication problems, given a set of pods (frontend) needs to communicate to another set of pods (backend), detailed as follows:

  • Given that the IP addresses may change, what are the valid IP addresses of the target pods?

  • Knowing the valid IP addresses, which pod should we communicate to?

Now, let's jump into the Kubernetes service as it is the solution for these two problems.

The Kubernetes service

The Kubernetes service is an abstraction of a grouping of sets of pods with a definition of how to access the pods. The set of pods targeted by a service is usually determined by a selector based on pod labels. The Kubernetes service also gets an IP address assigned, but it is virtual. The reason to call it a virtual IP address is that, from a node's perspective, there is neither a namespace nor a network interface bound to a service as there is with a pod. Also, unlike pods, the service is more stable, and its IP address is less likely to be changed frequently. Sounds like we should be able to solve the two problems mentioned earlier. First, define a service for the target sets of pods with a proper selector configured; secondly, let some magic associated with the service decide which target pod is to receive the request. So, when we look at pod-to-pod communication again, we're in fact talking about pod-to-service (then to-pod) communication.

So, what's the magic behind the service? Now, we'll introduce the great network magician: the kube-proxy component.

kube-proxy

你可以根据kube-proxy的名称猜到它的作用。一般来说,代理(不是反向代理)的作用是在客户端和服务器之间通过两个连接传递流量:从客户端到服务器的入站连接和从服务器到客户端的出站连接。因此,kube-proxy为了解决前面提到的两个问题,会将所有目标服务(虚拟 IP)的流量转发到由服务分组的 pod(实际 IP);同时,kube-proxy会监视 Kubernetes 控制平面,以便添加或删除服务和端点对象(pod)。为了很好地完成这个简单的任务,kube-proxy已经发展了几次。

用户空间代理模式

用户空间代理模式中的kube-proxy组件就像一个真正的代理。首先,kube-proxy将在节点上的一个随机端口上作为特定服务的代理端口进行监听。任何对代理端口的入站连接都将被转发到服务的后端 pod。当kube-proxy需要决定将请求发送到哪个后端 pod 时,它会考虑服务的SessionAffinity设置。其次,kube-proxy将安装iptables 规则,将任何目标服务(虚拟 IP)的流量转发到代理端口,代理端口再将流量转发到后端端口。来自 Kubernetes 文档的以下图表很好地说明了这一点:

图 2.6 - kube-proxy 用户空间代理模式

图 2.6 - kube-proxy 用户空间代理模式

默认情况下,用户空间模式中的kube-proxy使用循环算法来选择要将请求转发到的后端 pod。这种模式的缺点是显而易见的。流量转发是在用户空间中完成的。这意味着数据包被编组到用户空间,然后在代理的每次传输中被编组回内核空间。从性能的角度来看,这种解决方案并不理想。

iptables 代理模式

iptables 代理模式中的kube-proxy组件将转发流量的工作交给了netfilter,使用 iptables 规则。在 iptables 代理模式中的kube-proxy只负责维护和更新 iptables 规则。任何针对服务 IP 的流量都将根据kube-proxy管理的 iptables 规则由netfilter转发到后端 pod。来自 Kubernetes 文档的以下图表说明了这一点:

图 2.7 - kube-proxy iptables 代理模式

图 2.7 - kube-proxy iptables 代理模式

与用户空间代理模式相比,iptables 模式的优势是显而易见的。流量不再经过内核空间到用户空间,然后再返回内核空间。相反,它将直接在内核空间中转发。开销大大降低。这种模式的缺点是需要错误处理。例如,如果kube-proxy在 iptables 代理模式下运行,如果第一个选择的 pod 没有响应,连接将失败。然而,在用户空间模式下,kube-proxy会检测到与第一个 pod 的连接失败,然后自动重试与不同的后端 pod。

IPVS 代理模式

IP Virtual ServerIPVS)代理模式中的kube-proxy组件管理和利用 IPVS 规则,将目标服务流量转发到后端 pod。就像 iptables 规则一样,IPVS 规则也在内核中工作。IPVS 建立在netfilter之上。它作为 Linux 内核的一部分实现传输层负载均衡,纳入Linux Virtual ServerLVS)中。LVS 在主机上运行,并充当一组真实服务器前面的负载均衡器,任何传输控制协议(TCP)或用户数据报协议(UDP)流量都将被转发到真实服务器。这使得真实服务器的 IPVS 服务看起来像单个 IP 地址上的虚拟服务。IPVS 与 Kubernetes 服务完美匹配。以下来自 Kubernetes 文档的图表说明了这一点:

图 2.8 - kube-proxy IPVS 代理模式

图 2.8 - kube-proxy IPVS 代理模式

与 iptables 代理模式相比,IPVS 规则和 iptables 规则都在内核空间中工作。然而,iptables 规则会对每个传入的数据包进行顺序评估。规则越多,处理时间越长。IPVS 的实现与 iptables 不同:它使用由内核管理的哈希表来存储数据包的目的地,因此具有比 iptables 规则更低的延迟和更快的规则同步。IPVS 模式还提供了更多的负载均衡选项。使用 IPVS 模式的唯一限制是必须在节点上有可供kube-proxy使用的 IPVS Linux。

介绍 Kubernetes 服务

Kubernetes 部署动态创建和销毁 pod。对于一般的三层 Web 架构,如果前端和后端是不同的 pod,这可能是一个问题。前端 pod 不知道如何连接到后端。Kubernetes 中的网络服务抽象解决了这个问题。

Kubernetes 服务使一组逻辑 pod 能够进行网络访问。通常使用标签来定义一组逻辑 pod。当对服务发出网络请求时,它会选择所有具有特定标签的 pod,并将网络请求转发到所选 pod 中的一个。

使用YAML Ain't Markup Language (YAML)文件定义了 Kubernetes 服务,如下所示:

apiVersion: v1
kind: Service
metadata:
  name: service-1
spec:
  type: NodePort 
  selector:
    app: app-1
  ports:
    - nodePort: 29763
      protocol: TCP
      port: 80
      targetPort: 9376

在这个 YAML 文件中,以下内容适用:

  1. type属性定义了服务如何向网络公开。

  2. selector属性定义了 Pod 的标签。

  3. port属性用于定义在集群内部公开的端口。

  4. targetPort属性定义了容器正在侦听的端口。

服务通常使用选择器来定义,选择器是附加到需要在同一服务中的 pod 的标签。服务可以在没有选择器的情况下定义。这通常是为了访问外部服务或不同命名空间中的服务。没有选择器的服务将使用端点对象映射到网络地址和端口,如下所示:

apiVersion: v1
kind: Endpoints
subsets:
  - addresses:
      - ip: 192.123.1.22
    ports:
      - port: 3909

此端点对象将路由流量192:123.1.22:3909到附加的服务。

服务发现

要找到 Kubernetes 服务,开发人员可以使用环境变量或Domain Name System (DNS),详细如下:

  1. 环境变量:创建服务时,在节点上创建了一组环境变量,形式为[NAME]_SERVICE_HOST[NAME]_SERVICE_PORT。其他 pod 或应用程序可以使用这些环境变量来访问服务,如下面的代码片段所示:
DB_SERVICE_HOST=192.122.1.23
DB_SERVICE_PORT=3909
  1. DNS:DNS 服务作为附加组件添加到 Kubernetes 中。Kubernetes 支持两个附加组件:CoreDNS 和 Kube-DNS。DNS 服务包含服务名称到 IP 地址的映射。Pod 和应用程序使用此映射来连接到服务。

客户端可以通过环境变量和 DNS 查询来定位服务 IP,而且有不同类型的服务来为不同类型的客户端提供服务。

服务类型

服务可以有四种不同的类型,如下所示:

  • ClusterIP:这是默认值。此服务只能在集群内访问。可以使用 Kubernetes 代理来外部访问 ClusterIP 服务。使用kubectl代理进行调试是可取的,但不建议用于生产服务,因为它需要以经过身份验证的用户身份运行kubectl

  • NodePort:此服务可以通过每个节点上的静态端口访问。NodePorts 每个端口暴露一个服务,并需要手动管理 IP 地址更改。这也使得 NodePorts 不适用于生产环境。

  • LoadBalancer:此服务可以通过负载均衡器访问。通常每个服务都有一个节点负载均衡器是一个昂贵的选择。

  • ExternalName:此服务有一个关联的规范名称记录CNAME),用于访问该服务。

有几种要使用的服务类型,它们在 OSI 模型的第 3 层和第 4 层上工作。它们都无法在第 7 层路由网络请求。为了路由请求到应用程序,如果 Kubernetes 服务支持这样的功能将是理想的。那么,让我们看看 Ingress 对象如何在这里帮助。

用于路由外部请求的 Ingress

Ingress 不是一种服务类型,但在这里值得一提。Ingress 是一个智能路由器,为集群中的服务提供外部HTTP/HTTPS超文本传输安全协议)访问。除了 HTTP/HTTPS 之外的服务只能暴露给 NodePort 或 LoadBalancer 服务类型。Ingress 资源是使用 YAML 文件定义的,就像这样:

apiVersion: extensions/v1beta1
kind: Ingress
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: service-1
          servicePort: 80

这个最小的 Ingress 规范将testpath路由的所有流量转发到service-1路由。

Ingress 对象有五种不同的变体,列举如下:

  • 单服务 Ingress:通过指定默认后端和没有规则来暴露单个服务,如下面的代码块所示:
apiVersion: extensions/v1beta1
kind: Ingress
spec:
  backend:
    serviceName: service-1
    servicePort: 80

这个 Ingress 暴露了一个专用 IP 地址给service-1

  • 简单的分流:分流配置根据统一资源定位符URL)将来自单个 IP 的流量路由到多个服务,如下面的代码块所示:
apiVersion: extensions/v1beta1
kind: Ingress
spec:
  rules:
  - host: foo.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: service-1
          servicePort: 8080
      - path: /bar
        backend:
          serviceName: service-2
          servicePort: 8080

这个配置允许foo.com/foo的请求到达service-1,并且foo.com/bar连接到service-2

  • 基于名称的虚拟主机:此配置使用多个主机名来达到一个 IP 到达不同服务的目的,如下面的代码块所示:
apiVersion: extensions/v1beta1
kind: Ingress
spec:
  rules:
  - host: foo.com
    http:
      paths:
      - backend:
          serviceName: service-1
          servicePort: 80
  - host: bar.com
    http:
      paths:
      - backend:
          serviceName: service-2
          servicePort: 80

这个配置允许foo.com的请求连接到service-1bar.com的请求连接到service-2。在这种情况下,两个服务分配的 IP 地址是相同的。

  • 传输层安全性(TLS):可以向入口规范添加一个秘密以保护端点,如下面的代码块所示:
apiVersion: extensions/v1beta1
kind: Ingress
spec:
  tls:
  - hosts:
    - ssl.foo.com
    secretName: secret-tls
  rules:
    - host: ssl.foo.com
      http:
        paths:
        - path: /
          backend:
            serviceName: service-1
            servicePort: 443

通过这个配置,secret-tls提供了端点的私钥和证书。

  • 负载平衡:负载平衡入口提供负载平衡策略,其中包括所有入口对象的负载平衡算法和权重方案。

在本节中,我们介绍了 Kubernetes 服务的基本概念,包括入口对象。这些都是 Kubernetes 对象。然而,实际的网络通信魔术是由几个组件完成的,比如kube-proxy。接下来,我们将介绍 CNI 和 CNI 插件,这是为 Kubernetes 集群的网络通信提供服务的基础。

介绍 CNI 和 CNI 插件

在 Kubernetes 中,CNI 代表容器网络接口。CNI 是云原生计算基金会(CNCF)的一个项目-您可以在 GitHub 上找到更多信息:github.com/containernetworking/cni。基本上,这个项目有三个东西:一个规范,用于编写插件以配置 Linux 容器中的网络接口的库,以及一些支持的插件。当人们谈论 CNI 时,他们通常指的是规范或 CNI 插件。CNI 和 CNI 插件之间的关系是 CNI 插件是可执行的二进制文件,实现了 CNI 规范。现在,让我们高层次地看看 CNI 规范和插件,然后我们将简要介绍 CNI 插件之一,Calico。

CNI 规范和插件

CNI 规范只关注容器的网络连接性,并在容器删除时移除分配的资源。让我更详细地解释一下。首先,从容器运行时的角度来看,CNI 规范为容器运行时接口CRI)组件(如 Docker)定义了一个接口,用于与之交互,例如在创建容器时向网络接口添加容器,或者在容器死亡时删除网络接口。其次,从 Kubernetes 网络模型的角度来看,由于 CNI 插件实际上是 Kubernetes 网络插件的另一种类型,它们必须符合 Kubernetes 网络模型的要求,详细如下:

  1. 节点上的 pod 可以与所有节点上的所有 pod 进行通信,而无需使用 NAT。

  2. kubelet等代理可以与同一节点中的 pod 进行通信。

有一些可供选择的 CNI 插件,比如 Calico、Cilium、WeaveNet、Flannel 等。CNI 插件的实施各不相同,但总的来说,CNI 插件的功能类似。它们执行以下任务:

  • 管理容器的网络接口

  • 为 pod 分配 IP 地址。这通常是通过调用其他IP 地址管理IPAM)插件(如host-local)来完成的。

  • 实施网络策略(可选)

CNI 规范中不要求实施网络策略,但是当 DevOps 选择要使用的 CNI 插件时,考虑安全性是很重要的。Alexis Ducastel 的文章(itnext.io/benchmark-results-of-kubernetes-network-plugins-cni-over-10gbit-s-network-36475925a560)在 2019 年 4 月进行了主流 CNI 插件的良好比较。安全性比较值得注意,如下截图所示:

图 2.9 - CNI 插件比较

图 2.9 - CNI 插件比较

您可能会注意到列表中大多数 CNI 插件都不支持加密。Flannel 不支持 Kubernetes 网络策略,而kube-router仅支持入口网络策略。

由于 Kubernetes 默认使用kubenet插件,为了在 Kubernetes 集群中使用 CNI 插件,用户必须通过--network-plugin=cni命令行选项传递,并通过--cni-conf-dir标志或在/etc/cni/net.d默认目录中指定配置文件。以下是在 Kubernetes 集群中定义的示例配置,以便kubelet知道要与哪个 CNI 插件交互:

{
  'name': 'k8s-pod-network',
  'cniVersion': '0.3.0',
  'plugins': [
    {
      'type': 'calico',
      'log_level': 'info',
      'datastore_type': 'kubernetes',
      'nodename': '127.0.0.1',
      'ipam': {
        'type': 'host-local',
        'subnet': 'usePodCidr'
      },
      'policy': {
        'type': 'k8s'
      },
      'kubernetes': {
        'kubeconfig': '/etc/cni/net.d/calico-kubeconfig'
      }
    },
    {
      'type': 'portmap',
      'capabilities': {'portMappings': true}
    }
  ]
}

CNI 配置文件告诉kubelet使用 Calico 作为 CNI 插件,并使用host-local来为 pod 分配 IP 地址。在列表中,还有另一个名为portmap的 CNI 插件,用于支持hostPort,允许容器端口在主机 IP 上暴露。

在使用Kubernetes Operationskops)创建集群时,您还可以指定要使用的 CNI 插件,如下面的代码块所示:

  export NODE_SIZE=${NODE_SIZE:-m4.large}
  export MASTER_SIZE=${MASTER_SIZE:-m4.large}
  export ZONES=${ZONES:-'us-east-1d,us-east-1b,us-east-1c'}
  export KOPS_STATE_STORE='s3://my-state-store'
  kops create cluster k8s-clusters.example.com \
  --node-count 3 \
  --zones $ZONES \
  --node-size $NODE_SIZE \
  --master-size $MASTER_SIZE \
  --master-zones $ZONES \
  --networking calico \
  --topology private \
  --bastion='true' \
  --yes

在此示例中,集群是使用calico CNI 插件创建的。

Calico

Calico 是一个开源项目,可以实现云原生应用的连接和策略。它与主要的编排系统集成,如 Kubernetes、Apache Mesos、Docker 和 OpenStack。与其他 CNI 插件相比,Calico 有一些值得强调的优点:

  1. Calico 提供了一个扁平的 IP 网络,这意味着 IP 消息中不会附加 IP 封装(没有覆盖)。这也意味着分配给 pod 的每个 IP 地址都是完全可路由的。无需覆盖即可运行的能力提供了出色的吞吐特性。

  2. 根据 Alexis Ducastel 的实验,Calico 具有更好的性能和更少的资源消耗。

  3. 与 Kubernetes 内置的网络策略相比,Calico 提供了更全面的网络策略。Kubernetes 的网络策略只能定义白名单规则,而 Calico 网络策略可以定义黑名单规则(拒绝)。

将 Calico 集成到 Kubernetes 中时,您会看到以下三个组件在 Kubernetes 集群中运行:

  • calico/node是一个 DaemonSet 服务,这意味着它在集群中的每个节点上运行。它负责为本地工作负载编程和路由内核路由,并强制执行集群中当前网络策略所需的本地过滤规则。它还负责向其他节点广播路由表,以保持集群中 IP 路由的同步。

  • CNI 插件二进制文件。这包括两个可执行二进制文件(calicocalico-ipam)以及一个配置文件,直接与每个节点上的 Kubernetes kubelet进程集成。它监视 pod 创建事件,然后将 pod 添加到 Calico 网络中。

  • Calico Kubernetes 控制器作为一个独立的 pod 运行,监视 Kubernetes 应用程序编程接口API)以保持 Calico 同步。

Calico 是一个流行的 CNI 插件,也是Google Kubernetes EngineGKE)中的默认 CNI 插件。Kubernetes 管理员完全可以自由选择符合其要求的 CNI 插件。只需记住安全性是至关重要的决定因素之一。在前面的章节中,我们已经谈了很多关于 Kubernetes 网络的内容。在你忘记之前,让我们快速回顾一下。

总结

在 Kubernetes 集群中,每个 pod 都被分配了一个 IP 地址,但这是一个内部 IP 地址,无法从外部访问。同一 pod 中的容器可以通过名称网络接口相互通信,因为它们共享相同的网络命名空间。同一 pod 中的容器还需要解决端口资源冲突的问题;然而,这种情况发生的可能性非常小,因为应用程序在同一 pod 中的不同容器中运行,目的是特定的。此外,值得注意的是,同一 pod 中的容器可以通过共享卷、IPC 通道和进程信号进行网络通信。

Kubernetes 服务有助于稳定 pod 之间的通信,因为 pod 通常是短暂的。该服务也被分配了一个 IP 地址,但这是虚拟的,意味着没有为服务创建网络接口。kube-proxy网络魔术师实际上将所有流量路由到目标服务的后端 pod。kube-proxy有三种不同的模式:用户空间代理、iptables 代理和 IPVS 代理。Kubernetes 服务不仅提供了对 pod 之间通信的支持,还能够实现来自外部源的通信。

有几种方法可以公开服务,使其可以从外部源访问,例如 NodePort、LoadBalancer 和 ExternalName。此外,您可以创建一个 Ingress 对象来实现相同的目标。最后,虽然很难,但我们将使用以下单个图表来尝试整合我们在本章中要强调的大部分知识:

图 2.10 - 通信:pod 内部、pod 之间以及来自外部的源

图 2.10 - 通信:pod 内部,pod 之间,以及来自外部来源

几乎每个 Kubernetes 集群前面都有一个负载均衡器。根据我们之前提到的不同服务类型,这可能是一个通过负载均衡器公开的单个服务(这是服务A),或者它可以通过 NodePort 公开。这是服务B,在两个节点上使用节点端口30000来接受外部流量。虽然 Ingress 不是一种服务类型,但与 LoadBalancer 类型服务相比,它更强大且成本效益更高。服务C和服务D的路由由同一个 Ingress 对象控制。集群中的每个 pod 可能在前面的标注图中有一个内部通信拓扑。

总结

在本章中,我们首先讨论了典型的端口资源冲突问题,以及 Kubernetes 网络模型如何在避免这一问题的同时保持对从 VM 迁移到 Kubernetes pod 的应用程序的良好兼容性。然后,我们讨论了 pod 内部的通信,pod 之间的通信,以及来自外部来源到 pod 的通信。

最后但并非最不重要的是,我们介绍了 CNI 的基本概念,并介绍了 Calico 在 Kubernetes 环境中的工作原理。在前两章中,我们希望您对 Kubernetes 组件的工作方式以及各个组件之间的通信有了基本的了解。

在下一章中,我们将讨论威胁建模 Kubernetes 集群。

问题

  1. 在 Kubernetes 集群中,IP 地址分配给 pod 还是容器?

  2. 在同一个 pod 内部,哪些 Linux 命名空间将被容器共享?

  3. 暂停容器是什么,它有什么作用?

  4. Kubernetes 服务有哪些类型?

  5. 除了 LoadBalancer 类型的服务,使用 Ingress 的优势是什么?

进一步阅读

如果您想构建自己的 CNI 插件或评估 Calico 更多,请查看以下链接:

第三章:威胁建模

Kubernetes 是一个庞大的生态系统,包括多个组件,如kube-apiserveretcdkube-schedulerkubelet等。在第一章中,我们强调了不同 Kubernetes 组件的基本功能。在默认配置中,Kubernetes 组件之间的交互会导致开发人员和集群管理员应该意识到的威胁。此外,在 Kubernetes 中部署应用程序会引入应用程序与之交互的新实体,为应用程序的威胁模型增加新的威胁行为者和攻击面。

在本章中,我们将从简要介绍威胁建模开始,讨论 Kubernetes 生态系统内的组件交互。我们将研究默认 Kubernetes 配置中的威胁。最后,我们将讨论在 Kubernetes 生态系统中对应用进行威胁建模如何引入额外的威胁行为者和攻击面。

本章的目标是帮助您了解,默认的 Kubernetes 配置不足以保护您部署的应用免受攻击者的侵害。Kubernetes 是一个不断发展和由社区维护的平台,因此本章要突出的一些威胁并没有相应的缓解措施,因为威胁的严重程度会随着每个环境的不同而变化。

本章旨在突出 Kubernetes 生态系统中的威胁,其中包括 Kubernetes 集群中的 Kubernetes 组件和工作负载,以便开发人员和 DevOps 工程师了解其部署的风险,并制定已知威胁的风险缓解计划。在本章中,我们将涵盖以下主题:

  • 威胁建模介绍

  • 组件交互

  • Kubernetes 环境中的威胁行为者

  • Kubernetes 组件/对象威胁模型

  • 在 Kubernetes 中对应用程序进行威胁建模

威胁建模介绍

威胁建模是在软件开发生命周期SDLC)的设计阶段分析系统作为整体,以主动识别系统的风险的过程。威胁建模用于在开发周期的早期考虑安全需求,以从一开始减轻风险的严重性。威胁建模涉及识别威胁,了解每个威胁的影响,最终为每个威胁制定缓解策略。威胁建模旨在将生态系统中的风险突出显示为一个简单的矩阵,其中包括风险的可能性和影响,以及相应的风险缓解策略(如果存在)。

成功的威胁建模会使您能够定义以下内容:

  1. 资产:生态系统中需要保护的财产。

  2. 安全控制:系统的属性,用于保护资产免受已识别风险的影响。这些是对资产风险的防护措施或对策。

  3. 威胁行为者:威胁行为者是利用风险的实体或组织,包括脚本小子、国家级攻击者和黑客活动分子。

  4. 攻击面:威胁行为者与系统交互的部分。它包括威胁行为者进入系统的入口点。

  5. 威胁:对资产的风险。

  6. 缓解:缓解定义了如何减少对资产的威胁的可能性和影响。

行业通常遵循以下威胁建模方法之一:

  • STRIDE:STRIDE 模型于 1999 年由微软发布。它是欺骗、篡改、否认、信息泄露、拒绝服务和特权升级的首字母缩略词。STRIDE 模型威胁系统,以回答“系统可能出现什么问题?”的问题。

  • PASTA:攻击模拟和威胁分析过程是一种以风险为中心的威胁建模方法。PASTA 遵循以攻击者为中心的方法,由业务和技术团队开发以资产为中心的缓解策略。

  • VAST:Visual, Agile, and Simple Threat modeling 旨在将威胁建模整合到应用程序和基础架构开发中,与 SDLC 和敏捷软件开发相结合。它提供了一种可视化方案,为开发人员、架构师、安全研究人员和业务执行人员提供可操作的输出。

威胁建模还有其他方法,但前面三种是行业内最常用的方法。

如果威胁模型的范围没有明确定义,威胁建模可能是一个无限长的任务。在开始识别生态系统中的威胁之前,重要的是清楚地了解每个组件的架构和工作方式,以及组件之间的交互。

在前几章中,我们已经详细了解了每个 Kubernetes 组件的基本功能。现在,我们将在调查 Kubernetes 生态系统内的威胁之前,先看一下 Kubernetes 中不同组件之间的交互。

组件交互

Kubernetes 组件共同工作,以确保集群内运行的微服务能够如预期般运行。如果将微服务部署为 DaemonSet,则 Kubernetes 组件将确保每个节点上都有一个运行微服务的 pod,不多不少。那么在幕后会发生什么?让我们看一下高层次上组件之间的交互的图表:

图 3.1 - Kubernetes 组件之间的交互

图 3.1 - Kubernetes 组件之间的交互

这些组件的快速回顾:

  • kube-apiserver:Kubernetes API 服务器(kube-apiserver)是一个控制平面组件,用于验证和配置对象的数据。

  • etcd:etcd是一个高可用的键值存储,用于存储配置、状态和元数据等数据。

  • kube-scheduler:kube-scheduler是 Kubernetes 的默认调度程序。它监视新创建的 pod,并将 pod 分配给节点。

  • kube-controller-manager:Kubernetes 控制器管理器是一组核心控制器,它们监视状态更新并相应地对集群进行更改。

  • cloud-controller-manager:云控制器管理器运行控制器,与底层云提供商进行交互。

  • kubelet:kubelet向 API 服务器注册节点,并监视使用 Podspecs 创建的 pod,以确保 pod 和容器健康。

值得注意的是,只有kube-apiserveretcd通信。其他 Kubernetes 组件,如kube-schedulerkube-controller-managercloud-controller manager与运行在主节点上的kube-apiserver进行交互,以履行它们的责任。在工作节点上,kubeletkube-proxy都与kube-apiserver通信。

让我们以 DaemonSet 创建为例,展示这些组件如何相互通信:

图 3.2 - 在 Kubernetes 中创建 DaemonSet

图 3.2 - 在 Kubernetes 中创建 DaemonSet

要创建一个 DaemonSet,我们使用以下步骤:

  1. 用户通过 HTTPS 向kube-apiserver发送请求以创建 DaemonSet 工作负载。

  2. 经过身份验证、授权和对象验证后,kube-apiserveretcd数据库中为 DaemonSet 创建工作负载对象信息。默认情况下,etcd中的数据在传输和静止状态下都不加密。

  3. DaemonSet 控制器监视新的 DaemonSet 对象的创建,然后向kube-apiserver发送 pod 创建请求。请注意,DaemonSet 基本上意味着微服务将在每个节点的 pod 中运行。

  4. kube-apiserver重复步骤 2中的操作,并在etcd数据库中为 pod 创建工作负载对象信息。

  5. kube-scheduler监视新的 pod 的创建,然后根据节点选择标准决定在哪个节点上运行该 pod。之后,kube-schedulerkube-apiserver发送有关 pod 将在哪个节点上运行的请求。

  6. kube-apiserver接收来自kube-scheduler的请求,然后使用 pod 的节点分配信息更新etcd

  7. 运行在工作节点上的kubelet监视分配给该节点的新 pod,然后向容器运行时接口CRI)组件(如 Docker)发送请求以启动容器。之后,kubelet将 pod 的状态发送回kube-apiserver

  8. kube-apiserver从目标节点上的kubelet接收 pod 的状态信息,然后更新etcd数据库中的 pod 状态。

  9. 一旦创建了(来自 DaemonSet 的)pod,这些 pod 就能够与其他 Kubernetes 组件进行通信,微服务应该已经启动并运行。

请注意,并非所有组件之间的通信都默认安全。这取决于这些组件的配置。我们将在第六章中更详细地介绍这一点,保护集群组件

Kubernetes 环境中的威胁行为者

威胁行为者是系统中执行的应该受到保护的资产的实体或代码。从防御的角度来看,你首先需要了解你的潜在敌人是谁,否则你的防御策略将太模糊。Kubernetes 环境中的威胁行为者可以大致分为三类:

  1. 终端用户:可以连接到应用程序的实体。该参与者的入口点通常是负载均衡器或入口。有时,Pod、容器或 NodePorts 可能直接暴露在互联网上,为终端用户增加了更多的入口点。

  2. 内部攻击者:在 Kubernetes 集群内部具有有限访问权限的实体。集群内生成的恶意容器或 Pod 是内部攻击者的示例。

  3. 特权攻击者:在 Kubernetes 集群内部具有管理员访问权限的实体。基础设施管理员、被 compromise 的kube-apiserver实例和恶意节点都是特权攻击者的示例。

威胁参与者的示例包括脚本小子、骇客活动分子和国家行为者。所有这些参与者都属于前面提到的三类,取决于参与者在系统中的位置。

以下图表突出了 Kubernetes 生态系统中的不同参与者:

图 3.3– Kubernetes 环境中的威胁参与者

图 3.3– Kubernetes 环境中的威胁参与者

正如您在此图表中所看到的,终端用户通常与入口控制器、负载均衡器或 Pod 暴露的 HTTP/HTTPS 路由进行交互。终端用户权限最低。另一方面,内部攻击者对集群内部的资源具有有限访问权限。特权攻击者权限最高,并且有能力修改集群。这三类攻击者有助于确定威胁的严重程度。涉及终端用户的威胁比涉及特权攻击者的威胁具有更高的严重性。尽管这些角色在图表中似乎是孤立的,但攻击者可以通过权限提升攻击从终端用户变为内部攻击者。

Kubernetes 集群中的威胁

通过对 Kubernetes 组件和威胁参与者的新理解,我们将继续进行 Kubernetes 集群的威胁建模之旅。在下表中,我们涵盖了主要的 Kubernetes 组件、节点和 Pod。节点和 Pod 是运行工作负载的基本 Kubernetes 对象。请注意,所有这些组件都是资产,应该受到威胁的保护。这些组件中的任何一个被 compromise 都可能导致攻击的下一步,比如权限提升。还要注意,kube-apiserveretcd是 Kubernetes 集群的大脑和心脏。如果它们中的任何一个被 compromise,那将是游戏结束。

下表突出了默认 Kubernetes 配置中的威胁。该表还突出了开发人员和集群管理员如何保护其资产免受这些威胁的影响:

这张表只突出了一些威胁。还有更多的威胁,将在后面的章节中进行讨论。我们希望前面的表格能激发您对需要在 Kubernetes 集群中保护什么以及如何保护的思考。

在 Kubernetes 中进行威胁建模应用

现在我们已经看过 Kubernetes 集群中的威胁,让我们继续讨论在 Kubernetes 上部署的应用程序的威胁建模将会有何不同。在 Kubernetes 中部署会给威胁模型增加额外的复杂性。Kubernetes 增加了额外的考虑因素、资产、威胁行为者和需要在调查部署应用程序的威胁之前考虑的新安全控制。

让我们来看一个简单的三层 Web 应用的例子:

图 3.4 - 传统 Web 应用的威胁模型

图 3.4 - 传统 Web 应用的威胁模型

在 Kubernetes 环境中,同一应用看起来有些不同:

图 3.5 - Kubernetes 中三层 Web 应用的威胁模型

](image/B15566_03_005.jpg)

图 3.5 - Kubernetes 中三层 Web 应用的威胁模型

如前图所示,Web 服务器、应用服务器和数据库都在 pod 中运行。让我们对传统 Web 架构和云原生架构之间的威胁建模进行高层比较:

总结前面的比较,您会发现在云原生架构中需要保护更多的资产,并且在这个领域会面临更多的威胁行为者。Kubernetes 提供了更多的安全控制,但也增加了更多的复杂性。更多的安全控制并不一定意味着更安全。请记住:复杂性是安全的敌人。

总结

在本章中,我们首先介绍了威胁建模的基本概念。我们讨论了 Kubernetes 环境中的重要资产、威胁和威胁行为者。我们讨论了不同的安全控制和缓解策略,以改善您的 Kubernetes 集群的安全状况。

然后我们通过应用程序威胁建模,考虑了部署在 Kubernetes 中的应用程序,并将其与传统的单片应用程序的威胁建模进行了比较。Kubernetes 设计引入的复杂性使威胁建模变得更加复杂,正如我们所展示的:需要保护的资产更多,威胁行为者也更多。而更多的安全控制并不一定意味着更安全。

您应该记住,尽管威胁建模可能是一个漫长而复杂的过程,但值得去了解您环境的安全状况。同时进行应用程序威胁建模和基础设施威胁建模对于更好地保护您的 Kubernetes 集群非常必要。

在下一章中,为了帮助您了解如何将您的 Kubernetes 集群安全性提升到更高水平,我们将讨论最小特权原则以及如何在 Kubernetes 集群中实施它。

问题

  1. 何时开始对应用程序进行威胁建模?

  2. Kubernetes 环境中有哪些不同的威胁行为者?

  3. 提到默认 Kubernetes 部署的最严重的威胁之一。

  4. 为什么在 Kubernetes 环境中威胁建模更加困难?

  5. Kubernetes 部署的攻击面与传统架构中的部署相比如何?

进一步阅读

Trail of Bits 和 Atredis Partners 在 Kubernetes 组件的威胁建模方面做得很好。他们的白皮书详细介绍了每个 Kubernetes 组件中的威胁。您可以在github.com/kubernetes/community/blob/master/wg-security-audit/findings/Kubernetes%20Threat%20Model.pdf找到这份白皮书。

请注意,前述白皮书的威胁建模的意图、范围和方法是不同的。因此,结果会有些不同。

第四章:在 Kubernetes 中应用最小权限原则

最小权限原则规定生态系统的每个组件在其功能运行所需的数据和资源上应具有最小的访问权限。在多租户环境中,不同用户或对象可以访问多个资源。最小权限原则确保在这种环境中,如果用户或对象行为不端,对集群造成的损害是最小的。

在本章中,我们将首先介绍最小权限原则。鉴于 Kubernetes 的复杂性,我们将首先研究 Kubernetes 主题,然后是主题可用的权限。然后,我们将讨论 Kubernetes 对象的权限以及限制它们的可能方式。本章的目标是帮助您理解一些关键概念,如最小权限原则和基于角色的访问控制(RBAC)。在本章中,我们将讨论不同的 Kubernetes 对象,如命名空间、服务账户、角色和角色绑定,以及 Kubernetes 安全特性,如安全上下文、PodSecurityPolicy 和 NetworkPolicy,这些特性可以用来实现 Kubernetes 集群的最小权限原则。

在本章中,我们将涵盖以下主题:

  • 最小权限原则

  • Kubernetes 主题的最小权限

  • Kubernetes 工作负载的最小权限

最小权限原则

特权是执行操作的权限,例如访问资源或处理一些数据。最小特权原则是任何主体、用户、程序、进程等都应该只具有执行其功能所需的最低特权的想法。例如,Alice,一个普通的 Linux 用户,能够在自己的主目录下创建文件。换句话说,Alice 至少具有在她的主目录下创建文件的特权或权限。然而,Alice 可能无法在另一个用户的目录下创建文件,因为她没有这样做的特权或权限。如果 Alice 的日常任务中没有一个实际行使在主目录中创建文件的特权,但她确实有这样做的特权,那么机器的管理员就没有遵守最小特权原则。在本节中,我们将首先介绍授权模型的概念,然后我们将讨论实施最小特权原则的好处。

授权模型

当我们谈论最小特权时,大多数时候我们是在授权的背景下谈论的,在不同的环境中,会有不同的授权模型。例如,访问控制列表ACL)广泛用于 Linux 和网络防火墙,而 RBAC 用于数据库系统。环境的管理员也有责任定义授权策略,以确保基于系统中可用的授权模型的最小特权。以下列表定义了一些流行的授权模型:

  • ACL:ACL 定义了与对象关联的权限列表。它指定了哪些主体被授予对对象的访问权限,以及对给定对象允许的操作。例如,-rw文件权限是文件所有者的读写权限。

  • RBAC:授权决策基于主体的角色,其中包含一组权限或特权。例如,在 Linux 中,用户被添加到不同的组(如staff)以授予对文件夹的访问权限,而不是单独被授予对文件系统上文件夹的访问权限。

  • 基于属性的访问控制(ABAC):授权决策基于主体的属性,例如标签或属性。基于属性的规则检查用户属性,如user.id="12345"user.project="project"user.status="active",以决定用户是否能够执行任务。

Kubernetes 支持 ABAC 和 RBAC。尽管 ABAC 功能强大且灵活,但在 Kubernetes 中的实施使其难以管理和理解。因此,建议在 Kubernetes 中启用 RBAC 而不是 ABAC。除了 RBAC,Kubernetes 还提供了多种限制资源访问的方式。在接下来的部分中我们将探讨 Kubernetes 中的 RBAC 和 ABAC 之前,让我们讨论确保最小特权的好处。

最小特权原则的奖励

尽管可能需要相当长的时间来理解主体的最低特权是为了执行其功能,但如果最小特权原则已经在您的环境中实施,奖励也是显著的:

  • 更好的安全性:通过实施最小特权原则,可以减轻内部威胁、恶意软件传播、横向移动等问题。爱德华·斯诺登的泄密事件发生是因为缺乏最小特权。

  • 更好的稳定性:鉴于主体只被适当地授予必要的特权,主体的活动变得更加可预测。作为回报,系统的稳定性得到了加强。

  • 改进的审计准备性:鉴于主体只被适当地授予必要的特权,审计范围将大大减少。此外,许多常见的法规要求实施最小特权原则作为合规要求。

既然您已经看到了实施最小特权原则的好处,我也想介绍一下挑战:Kubernetes 的开放性和可配置性使得实施最小特权原则变得繁琐。让我们看看如何将最小特权原则应用于 Kubernetes 主体。

Kubernetes 主体的最小特权

Kubernetes 服务账户、用户和组与kube-apiserver通信,以管理 Kubernetes 对象。启用 RBAC 后,不同的用户或服务账户可能具有操作 Kubernetes 对象的不同特权。例如,system:master组中的用户被授予cluster-admin角色,这意味着他们可以管理整个 Kubernetes 集群,而system:kube-proxy组中的用户只能访问kube-proxy组件所需的资源。首先,让我们简要介绍一下 RBAC 是什么。

RBAC 简介

正如前面讨论的,RBAC 是一种基于授予用户或组角色的资源访问控制模型。从 1.6 版本开始,Kubernetes 默认启用了 RBAC。在 1.6 版本之前,可以通过使用带有--authorization-mode=RBAC标志的应用程序编程接口API)服务器来启用 RBAC。RBAC 通过 API 服务器简化了权限策略的动态配置。

RBAC 的核心元素包括以下内容:

  1. 主体:请求访问 Kubernetes API 的服务账户、用户或组。

  2. 资源:需要被主体访问的 Kubernetes 对象。

  3. 动词:主体在资源上需要的不同类型访问,例如创建、更新、列出、删除。

Kubernetes RBAC 定义了主体和它们在 Kubernetes 生态系统中对不同资源的访问类型。

服务账户、用户和组

Kubernetes 支持三种类型的主体,如下:

  • 普通用户:这些用户是由集群管理员创建的。它们在 Kubernetes 生态系统中没有对应的对象。集群管理员通常使用轻量级目录访问协议LDAP)、Active DirectoryAD)或私钥来创建用户。

  • 服务账户:Pod 使用服务账户对kube-apiserver对象进行身份验证。服务账户是通过 API 调用创建的。它们受限于命名空间,并且有关联的凭据存储为secrets。默认情况下,pod 使用default服务账户进行身份验证。

  • 匿名用户:任何未与普通用户或服务账户关联的 API 请求都与匿名用户关联。

集群管理员可以通过运行以下命令创建与 pod 关联的新服务账户:

$ kubectl create serviceaccount new_account

在默认命名空间中将创建一个new_account服务账户。为了确保最小权限,集群管理员应将每个 Kubernetes 资源与具有最小权限的服务账户关联起来。

角色

角色是权限的集合——例如,命名空间 A 中的角色可以允许用户在命名空间 A 中创建 pods 并列出命名空间 A 中的 secrets。在 Kubernetes 中,没有拒绝权限。因此,角色是一组权限的添加。

角色受限于命名空间。另一方面,ClusterRole 在集群级别工作。用户可以创建跨整个集群的 ClusterRole。ClusterRole 可用于调解对跨集群的资源的访问,例如节点、健康检查和跨多个命名空间的对象,例如 pods。以下是一个角色定义的简单示例:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: role-1
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get"]

这个简单的规则允许get操作超越默认命名空间中的pods资源。可以通过执行以下命令使用 kubectl 创建此角色:

$ kubectl apply -f role.yaml

如果以下任一条件为真,则用户只能创建或修改角色:

  • 用户在相同范围(命名空间或整个集群)中拥有角色中包含的所有权限。

  • 用户与给定范围内的升级角色相关联。

这可以防止用户通过修改用户角色和权限来执行权限升级攻击。

RoleBinding

RoleBinding 对象用于将角色与主体关联。与 ClusterRole 类似,ClusterRoleBinding 可以向跨命名空间的主体授予一组权限。让我们看几个例子:

  1. 创建一个 RoleBinding 对象,将“custom-clusterrole”集群角色与默认命名空间中的demo-sa服务账户关联起来,就像这样:
kubectl create rolebinding new-rolebinding-sa \
     --clusterrole=custom-clusterrole \
     --serviceaccount=default:demo-sa
  1. 创建一个 RoleBinding 对象,将custom-clusterrole集群角色与group-1组关联起来,就像这样:
kubectl create rolebinding new-rolebinding-group \
     --clusterrole=custom-clusterrole \
     --group=group-1 \
     --namespace=namespace-1

RoleBinding 对象将角色链接到主体,并使角色可重用且易于管理。

Kubernetes 命名空间

命名空间是计算机科学中的一个常见概念,为相关资源提供了逻辑分组。命名空间用于避免名称冲突;同一命名空间内的资源应具有唯一名称,但跨命名空间的资源可以共享名称。在 Linux 生态系统中,命名空间允许隔离系统资源。

在 Kubernetes 中,命名空间允许多个团队和项目在逻辑上共享单个集群。使用 Kubernetes 命名空间,以下内容适用:

  • 它们允许不同的应用程序、团队和用户在同一个集群中工作。

  • 它们允许集群管理员为应用程序使用命名空间资源配额。

  • 它们使用 RBAC 策略来控制对命名空间内特定资源的访问。RoleBinding 帮助集群管理员控制对命名空间内用户授予的权限。

  • 它们允许在命名空间中使用网络策略进行网络分割。默认情况下,所有 pod 可以跨不同命名空间相互通信。

默认情况下,Kubernetes 有三个不同的命名空间。运行以下命令查看它们:

$ kubectl get namespace
NAME          STATUS    AGE
default       Active    1d
kube-system   Active    1d
kube-public   Active    1d

三个命名空间的描述如下:

  • default:不属于任何其他命名空间的资源的命名空间。

  • kube-system:Kubernetes 创建的对象的命名空间,如kube-apiserverkube-schedulercontroller-managercoredns

  • kube-public:此命名空间内的资源对所有人都是可访问的。默认情况下,此命名空间中不会创建任何内容。

让我们看看如何创建一个命名空间。

创建命名空间

可以使用以下命令在 Kubernetes 中创建新的命名空间:

$ kubectl create namespace test

创建新的命名空间后,可以使用namespace属性将对象分配给命名空间,如下所示:

$ kubectl apply --namespace=test -f pod.yaml

同样地,可以使用namespace属性访问命名空间内的对象,如下所示:

$ kubectl get pods --namespace=test

在 Kubernetes 中,并非所有对象都有命名空间。低级别对象如NodespersistentVolumes跨越多个命名空间。

为 Kubernetes 主体实现最小特权

到目前为止,您应该熟悉 ClusterRole/Role、ClusterRoleBinding/RoleBinding、服务账户和命名空间的概念。为了为 Kubernetes 主体实现最小特权,您可以在创建 Kubernetes 中的 Role 或 RoleBinding 对象之前问自己以下问题:

  • 主体是否需要在命名空间内或跨命名空间拥有权限?

这很重要,因为一旦主体具有集群级别的权限,它可能能够在所有命名空间中行使权限。

  • 权限应该授予用户、组还是服务账户?

当您向一个组授予一个角色时,这意味着组中的所有用户将自动获得新授予角色的特权。在向组授予角色之前,请确保您了解其影响。其次,Kubernetes 中的用户是为人类而设,而服务账户是为 pod 中的微服务而设。请确保您了解 Kubernetes 用户的责任,并相应地分配特权。另外,请注意,一些微服务根本不需要任何特权,因为它们不直接与kube-apiserver或任何 Kubernetes 对象进行交互。

  • 主体需要访问哪些资源?

在创建角色时,如果不指定资源名称或在resourceNames字段中设置*,则意味着已授予对该资源类型的所有资源的访问权限。如果您知道主体将要访问的资源名称,请在创建角色时指定资源名称。

Kubernetes 主体使用授予的特权与 Kubernetes 对象进行交互。了解您的 Kubernetes 主体执行的实际任务将有助于您正确授予特权。

Kubernetes 工作负载的最小特权

通常,将会有一个(默认)服务账户与 Kubernetes 工作负载相关联。因此,pod 内的进程可以使用服务账户令牌与kube-apiserver通信。DevOps 应该仔细地为服务账户授予必要的特权,以实现最小特权的目的。我们在前一节已经介绍过这一点。

除了访问kube-apiserver来操作 Kubernetes 对象之外,pod 中的进程还可以访问工作节点上的资源以及集群中的其他 pod/微服务(在第二章Kubernetes 网络中有介绍)。在本节中,我们将讨论对系统资源、网络资源和应用程序资源进行最小特权访问的可能实现。

访问系统资源的最小特权

请记住,运行在容器或 pod 内的微服务只是工作节点上的一个进程,在其自己的命名空间中隔离。根据配置,pod 或容器可以访问工作节点上的不同类型的资源。这由安全上下文控制,可以在 pod 级别和容器级别进行配置。配置 pod/容器安全上下文应该是开发人员的任务清单(在安全设计和审查的帮助下),而限制 pod/容器访问集群级别系统资源的另一种方式——pod 安全策略,应该是 DevOps 的任务清单。让我们深入了解安全上下文、Pod 安全策略和资源限制控制的概念。

安全上下文

安全上下文提供了一种方式来定义与访问系统资源相关的 pod 和容器的特权和访问控制设置。在 Kubernetes 中,pod 级别的安全上下文与容器级别的安全上下文不同,尽管它们有一些重叠的属性可以在两个级别进行配置。总的来说,安全上下文提供了以下功能,允许您为容器和 pod 应用最小特权原则:

  • 自主访问控制(DAC):这是用来配置将哪个用户 ID(UID)或组 ID(GID)绑定到容器中的进程,容器的根文件系统是否为只读等。强烈建议不要在容器中以 root 用户(UID = 0)身份运行您的微服务。安全影响是,如果存在漏洞并且容器逃逸到主机,攻击者立即获得主机上的 root 用户权限。

  • 安全增强 Linux(SELinux):这是用来配置 SELinux 安全上下文的,它为 pod 或容器定义了级别标签、角色标签、类型标签和用户标签。通过分配 SELinux 标签,pod 和容器可能会受到限制,特别是在能够访问节点上的卷方面。

  • 特权模式:这是用来配置容器是否在特权模式下运行。特权容器内运行的进程的权限基本上与节点上的 root 用户相同。

  • Linux 功能: 这是为容器配置 Linux 功能。不同的 Linux 功能允许容器内的进程执行不同的活动或在节点上访问不同的资源。例如,CAP_AUDIT_WRITE允许进程写入内核审计日志,而CAP_SYS_ADMIN允许进程执行一系列管理操作。

  • AppArmor: 这是为 Pod 或容器配置 AppArmor 配置文件。AppArmor 配置文件通常定义了进程拥有哪些 Linux 功能,容器可以访问哪些网络资源和文件等。

  • 安全计算模式(seccomp): 这是为 Pod 或容器配置 seccomp 配置文件。seccomp 配置文件通常定义了允许执行的系统调用白名单和将被阻止在 Pod 或容器内执行的系统调用黑名单。

  • AllowPrivilegeEscalation: 这是用于配置进程是否可以获得比其父进程更多的权限。请注意,当容器以特权运行或具有CAP_SYS_ADMIN功能时,AllowPrivilegeEscalation始终为真。

我们将在第八章中更多地讨论安全上下文,保护 Pods

PodSecurityPolicy

PodSecurityPolicy 是 Kubernetes 集群级别的资源,用于控制与安全相关的 Pod 规范属性。它定义了一组规则。当要在 Kubernetes 集群中创建 Pod 时,Pod 需要遵守 PodSecurityPolicy 中定义的规则,否则将无法启动。PodSecurityPolicy 控制或应用以下属性:

  • 允许运行特权容器

  • 允许使用主机级别的命名空间

  • 允许使用主机端口

  • 允许使用不同类型的卷

  • 允许访问主机文件系统

  • 要求容器运行只读根文件系统

  • 限制容器的用户 ID 和组 ID

  • 限制容器的特权升级

  • 限制容器的 Linux 功能

  • 需要使用 SELinux 安全上下文

  • 将 seccomp 和 AppArmor 配置文件应用于 Pod

  • 限制 Pod 可以运行的 sysctl

  • 允许使用proc挂载类型

  • 限制 FSGroup 对卷的使用

我们将在《第八章》《Securing Kubernetes Pods》中更多地介绍 PodSecurityPolicy。PodSecurityPolicy 控制基本上是作为一个准入控制器实现的。您也可以创建自己的准入控制器,为您的工作负载应用自己的授权策略。Open Policy AgentOPA)是另一个很好的选择,可以为工作负载实现自己的最小特权策略。我们将在《第七章》《Authentication, Authorization, and Admission Control》中更多地了解 OPA。

现在,让我们看一下 Kubernetes 中的资源限制控制机制,因为您可能不希望您的微服务饱和系统中的所有资源,比如Central Processing UnitCPU)和内存。

资源限制控制

默认情况下,单个容器可以使用与节点相同的内存和 CPU 资源。运行加密挖矿二进制文件的容器可能会轻松消耗节点上其他 Pod 共享的 CPU 资源。为工作负载设置资源请求和限制始终是一个良好的实践。资源请求会影响调度器分配 Pod 的节点,而资源限制设置了容器终止的条件。为您的工作负载分配更多的资源请求和限制以避免驱逐或终止始终是安全的。但是,请记住,如果您将资源请求或限制设置得太高,您将在集群中造成资源浪费,并且分配给您的工作负载的资源可能无法充分利用。我们将在《第十章》《Real-Time Monitoring and Resource Management of a Kubernetes Cluster》中更多地介绍这个话题。

封装访问系统资源的最小特权

当 pod 或容器以特权模式运行时,与非特权 pod 或容器不同,它们具有与节点上的管理员用户相同的特权。如果您的工作负载以特权模式运行,为什么会这样?当一个 pod 能够访问主机级别的命名空间时,该 pod 可以访问主机级别的资源,如网络堆栈、进程和进程间通信IPC)。但您真的需要授予主机级别的命名空间访问权限或设置特权模式给您的 pod 或容器吗?此外,如果您知道容器中的进程需要哪些 Linux 功能,最好放弃那些不必要的功能。您的工作负载需要多少内存和 CPU 才能完全正常运行?请考虑这些问题,以实现对您的 Kubernetes 工作负载的最小特权原则。正确设置资源请求和限制,为您的工作负载使用安全上下文,并为您的集群强制执行 PodSecurityPolicy。所有这些都将有助于确保您的工作负载以最小特权访问系统资源。

访问网络资源的最小特权

默认情况下,同一 Kubernetes 集群中的任何两个 pod 可以相互通信,如果在 Kubernetes 集群外没有配置代理规则或防火墙规则,一个 pod 可能能够与互联网通信。Kubernetes 的开放性模糊了微服务的安全边界,我们不应忽视容器或 pod 可以访问的其他微服务提供的 API 端点等网络资源。

假设您的工作负载(pod X)在名称空间 X 中只需要访问名称空间 NS1 中的另一个微服务 A;同时,名称空间 NS2 中有微服务 B。微服务 A 和微服务 B 都公开其表述状态传输RESTful)端点。默认情况下,您的工作负载可以访问微服务 A 和 B,假设微服务级别没有身份验证或授权,以及名称空间 NS1 和 NS2 中没有强制执行网络策略。请看下面的图表,说明了这一点:

图 4.1-没有网络策略的网络访问

图 4.1-没有网络策略的网络访问

在前面的图中,Pod X能够访问这两个微服务,尽管它们位于不同的命名空间中。还要注意,Pod X只需要访问NS1命名空间中的Microservice A。那么,我们是否可以做一些事情,以限制Pod X仅出于最小特权的目的访问Microservice A?是的:Kubernetes 网络策略可以帮助。我们将在第五章中更详细地介绍网络策略,配置 Kubernetes 安全边界。一般来说,Kubernetes 网络策略定义了一组 Pod 允许如何相互通信以及与其他网络端点通信的规则。您可以为您的工作负载定义入口规则和出口规则。

注意

入口规则:定义哪些来源被允许与受网络策略保护的 Pod 通信的规则。

出口规则:定义哪些目的地被允许与受网络策略保护的 Pod 通信的规则。

在下面的示例中,为了在Pod X中实现最小特权原则,您需要在Namespace X中定义一个网络策略,其中包含一个出口规则,指定只允许Microservice A

图 4.2 - 网络策略阻止对微服务 B 的访问

图 4.2 - 网络策略阻止对微服务 B 的访问

在前面的图中,Namespace X中的网络策略阻止了来自Pod XMicroservice B的任何请求,而Pod X仍然可以访问Microservice A,这是预期的。在您的网络策略中定义出口规则将有助于确保您的工作负载访问网络资源的最小特权。最后但同样重要的是,我们仍然需要从最小特权的角度关注应用程序资源级别。

访问应用程序资源的最小特权

虽然这个话题属于应用程序安全的范畴,但在这里提起也是值得的。如果有应用程序允许您的工作负载访问,并支持具有不同特权级别的多个用户,最好检查您的工作负载所代表的用户被授予的特权是否是必要的。例如,负责审计的用户不需要任何写入特权。应用程序开发人员在设计应用程序时应牢记这一点。这有助于确保您的工作负载访问应用程序资源的最小特权。

总结

在本章中,我们讨论了最小特权的概念。然后,我们讨论了 Kubernetes 中的安全控制机制,帮助在两个领域实现最小特权原则:Kubernetes 主体和 Kubernetes 工作负载。值得强调的是,全面实施最小特权原则的重要性。如果在任何领域中都忽略了最小特权,这可能会留下一个攻击面。

Kubernetes 提供了内置的安全控制,以实现最小特权原则。请注意,这是从开发到部署的一个过程:应用程序开发人员应与安全架构师合作,为与应用程序关联的服务账户设计最低特权,以及最低功能和适当的资源分配。在部署过程中,DevOps 应考虑使用 PodSecurityPolicy 和网络策略来强制执行整个集群的最小特权。

在下一章中,我们将从不同的角度看待 Kubernetes 的安全性:了解不同类型资源的安全边界以及如何加固它们。

问题

  1. 在 Kubernetes 中,什么是 Role 对象?

  2. 在 Kubernetes 中,什么是 RoleBinding 对象?

  3. RoleBinding 和 ClusterRoleBinding 对象之间有什么区别?

  4. 默认情况下,Pod 无法访问主机级命名空间。列举一些允许 Pod 访问主机级命名空间的设置。

  5. 如果您想限制 Pod 访问外部网络资源(例如内部网络或互联网),您可以做什么?

进一步阅读

您可能已经注意到,我们在本章中讨论的一些安全控制机制已经存在很长时间:SELinux 多类别安全/多级安全(MCS/MLS),AppArmor,seccomp,Linux 功能等。已经有许多书籍或文章介绍了这些技术。我鼓励您查看以下材料,以更好地了解如何使用它们来实现 Kubernetes 中的最小特权目标:

第五章:配置 Kubernetes 安全边界

安全边界分隔了安全域,其中一组实体共享相同的安全关注和访问级别,而信任边界是程序执行和数据改变信任级别的分界线。安全边界中的控制确保在边界之间移动的执行不会在没有适当验证的情况下提升信任级别。如果数据或执行在没有适当控制的情况下在安全边界之间移动,安全漏洞就会出现。

在本章中,我们将讨论安全和信任边界的重要性。我们将首先重点介绍介绍,以澄清安全和信任边界之间的任何混淆。然后,我们将深入了解 Kubernetes 生态系统中的安全域和安全边界。最后,我们将看一些增强 Kubernetes 中应用程序安全边界的功能。

您应该了解安全域和安全边界的概念,并了解基于底层容器技术以及内置安全功能(如 PodSecurityPolicy 和 NetworkPolicy)构建的 Kubernetes 周围的安全边界。

本章将涵盖以下主题:

  • 安全边界的介绍

  • 安全边界与信任边界

  • Kubernetes 安全域

  • Kubernetes 实体作为安全边界

  • 系统层的安全边界

  • 网络层的安全边界

安全边界的介绍

安全边界存在于数据层、网络层和系统层。安全边界取决于 IT 部门或基础设施团队使用的技术。例如,公司使用虚拟机来管理他们的应用程序- hypervisor 是虚拟机的安全边界。Hypervisor 确保在虚拟机中运行的代码不会逃离虚拟机或影响物理节点。当公司开始采用微服务并使用编排器来管理他们的应用程序时,容器是安全边界之一。然而,与虚拟机监视器相比,容器并不提供强大的安全边界,也不打算提供。容器在应用程序层强制执行限制,但不能阻止攻击者从内核层绕过这些限制。

在网络层,传统上,防火墙为应用程序提供了强大的安全边界。在微服务架构中,Kubernetes 中的 Pod 可以相互通信。网络策略用于限制 Pod 和服务之间的通信。

数据层的安全边界是众所周知的。内核限制对系统或 bin 目录的写访问仅限于 root 用户或系统用户是数据层安全边界的一个简单例子。在容器化环境中,chroot 防止容器篡改其他容器的文件系统。Kubernetes 重新构建了应用程序部署的方式,可以在网络和系统层上强制执行强大的安全边界。

安全边界与信任边界

安全边界和信任边界经常被用作同义词。虽然相似,但这两个术语之间有微妙的区别。信任边界是系统改变其信任级别的地方。执行信任边界是指指令需要不同的特权才能运行的地方。例如,数据库服务器在/bin中执行代码就是执行越过信任边界的一个例子。同样,数据信任边界是指数据在不同信任级别的实体之间移动的地方。用户插入到受信任数据库中的数据就是数据越过信任边界的一个例子。

安全边界是不同安全域之间的分界点,安全域是一组在相同访问级别内的实体。例如,在传统的 Web 架构中,面向用户的应用程序是安全域的一部分,而内部网络是不同安全域的一部分。安全边界有与之相关的访问控制。将信任边界看作墙,将安全边界看作围绕墙的栅栏。

在生态系统中识别安全和信任边界是很重要的。这有助于确保在指令和数据跨越边界之前进行适当的验证。在 Kubernetes 中,组件和对象跨越不同的安全边界。了解这些边界对于在攻击者跨越安全边界时制定风险缓解计划至关重要。CVE-2018-1002105 是一个缺少跨信任边界验证而导致的攻击的典型例子;API 服务器中的代理请求处理允许未经身份验证的用户获得对集群的管理员特权。同样,CVE-2018-18264 允许用户跳过仪表板上的身份验证过程,以允许未经身份验证的用户访问敏感的集群信息。

现在让我们来看看不同的 Kubernetes 安全领域。

Kubernetes 安全领域

Kubernetes 集群可以大致分为三个安全领域:

  • Kubernetes 主组件:Kubernetes 主组件定义了 Kubernetes 生态系统的控制平面。主组件负责决策,以确保集群的顺利运行,如调度。主组件包括kube-apiserveretcdkube-controller管理器、DNS 服务器和kube-scheduler。Kubernetes 主组件的违规行为可能会危及整个 Kubernetes 集群。

  • Kubernetes 工作组件:Kubernetes 工作组件部署在每个工作节点上,确保 Pod 和容器正常运行。Kubernetes 工作组件使用授权和 TLS 隧道与主组件进行通信。即使工作组件受到损害,集群也可以正常运行。这类似于环境中的一个恶意节点,在识别后可以从集群中移除。

  • Kubernetes 对象:Kubernetes 对象是表示集群状态的持久实体:部署的应用程序、卷和命名空间。Kubernetes 对象包括 Pods、Services、卷和命名空间。这些是由开发人员或 DevOps 部署的。对象规范为对象定义了额外的安全边界:使用 SecurityContext 定义 Pod、与其他 Pod 通信的网络规则等。

高级安全领域划分应该帮助您专注于关键资产。记住这一点,我们将开始查看 Kubernetes 实体和围绕它们建立的安全边界。

Kubernetes 实体作为安全边界

在 Kubernetes 集群中,您与之交互的 Kubernetes 实体(对象和组件)都有其自己内置的安全边界。这些安全边界源自实体的设计或实现。了解实体内部或周围构建的安全边界非常重要:

  • 容器:容器是 Kubernetes 集群中的基本组件。容器使用 cgroups、Linux 命名空间、AppArmor 配置文件和 seccomp 配置文件为应用程序提供最小的隔离。

  • Pods:Pod 是一个或多个容器的集合。与容器相比,Pod 隔离更多资源,例如网络和 IPC。诸如 SecurityContext、NetworkPolicy 和 PodSecurityPolicy 之类的功能在 Pod 级别工作,以确保更高级别的隔离。

  • 节点:Kubernetes 中的节点也是安全边界。可以使用nodeSelectors指定 Pod 在特定节点上运行。内核和虚拟化程序强制执行运行在节点上的 Pod 的安全控制。诸如 AppArmor 和 SELinux 之类的功能可以帮助改善安全姿态,以及其他主机加固机制。

  • 集群:集群是一组 Pod、容器以及主节点和工作节点上的组件。集群提供了强大的安全边界。在集群内运行的 Pod 和容器在网络和系统层面上与其他集群隔离。

  • 命名空间:命名空间是隔离 Pod 和服务的虚拟集群。LimitRanger 准入控制器应用于命名空间级别,以控制资源利用和拒绝服务攻击。网络策略可以应用于命名空间级别。

  • Kubernetes API 服务器:Kubernetes API 服务器与所有 Kubernetes 组件交互,包括etcdcontroller-manager和集群管理员用于配置集群的kubelet。它调解与主组件的通信,因此集群管理员无需直接与集群组件交互。

我们在第三章中讨论了三种不同的威胁行为者,威胁建模:特权攻击者、内部攻击者和最终用户。这些威胁行为者也可能与前述的 Kubernetes 实体进行交互。我们将看到攻击者面对这些实体的安全边界:

  • 最终用户:最终用户与入口、暴露的 Kubernetes 服务或直接与节点上的开放端口进行交互。对于最终用户,节点、Pod、kube-apiserver和外部防火墙保护集群组件免受危害。

  • 内部攻击者:内部攻击者可以访问 Pod 和容器。由kube-apiserver强制执行的命名空间和访问控制可以防止这些攻击者提升权限或者危害集群。网络策略和 RBAC 控制可以防止横向移动。

  • 特权攻击者kube-apiserver是唯一保护主控件组件免受特权攻击者危害的安全边界。如果特权攻击者危害了kube-apiserver,那就完了。

在本节中,我们从用户的角度看了安全边界,并向您展示了 Kubernetes 生态系统中如何构建安全边界。接下来,让我们从微服务的角度来看系统层的安全边界。

系统层的安全边界

微服务运行在 Pod 内,Pod 被调度在集群中的工作节点上运行。在之前的章节中,我们已经强调容器是分配了专用 Linux 命名空间的进程。一个容器或 Pod 消耗了工作节点提供的所有必要资源。因此,了解系统层的安全边界以及如何加固它是很重要的。在本节中,我们将讨论基于 Linux 命名空间和 Linux 能力一起为微服务构建的安全边界。

Linux namespaces 作为安全边界

Linux namespaces 是 Linux 内核的一个特性,用于分隔资源以进行隔离。分配了命名空间后,一组进程看到一组资源,而另一组进程看到另一组资源。我们已经在第二章Kubernetes Networking中介绍了 Linux namespaces。默认情况下,每个 Pod 都有自己的网络命名空间和 IPC 命名空间。同一 Pod 中的每个容器都有自己的 PID 命名空间,因此一个容器不知道同一 Pod 中运行的其他容器。同样,一个 Pod 不知道同一工作节点中存在其他 Pod。

一般来说,默认设置在安全方面为微服务提供了相当好的隔离。然而,允许在 Kubernetes 工作负载中配置主机命名空间设置,更具体地说,在 Pod 规范中。启用这样的设置后,微服务将使用主机级别的命名空间:

  • HostNetwork:Pod 使用主机的网络命名空间。

  • HostIPC:Pod 使用主机的 IPC 命名空间。

  • HostPID:Pod 使用主机的 PID 命名空间。

  • shareProcessNamespace:同一 Pod 内的容器将共享一个 PID 命名空间。

当您尝试配置工作负载以使用主机命名空间时,请问自己一个问题:为什么您必须这样做?当使用主机命名空间时,Pod 在同一工作节点中对其他 Pod 的活动有完全的了解,但这也取决于为容器分配了哪些 Linux 功能。总的来说,事实是,您正在削弱其他微服务的安全边界。让我举个快速的例子。这是容器内可见的进程列表:

root@nginx-2:/# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.1  0.0  32648  5256 ?        Ss   23:47   0:00 nginx: master process nginx -g daemon off;
nginx          6  0.0  0.0  33104  2348 ?        S    23:47   0:00 nginx: worker process
root           7  0.0  0.0  18192  3248 pts/0    Ss   23:48   0:00 bash
root          13  0.0  0.0  36636  2816 pts/0    R+   23:48   0:00 ps aux

正如您所看到的,在nginx容器内,只有nginx进程和bash进程从容器中可见。这个nginx Pod 没有使用主机 PID 命名空间。让我们看看如果一个 Pod 使用主机 PID 命名空间会发生什么:

root@gke-demo-cluster-default-pool-c9e3510c-tfgh:/# ps axu
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.2  0.0  99660  7596 ?        Ss   22:54   0:10 /usr/lib/systemd/systemd noresume noswap cros_efi
root          20  0.0  0.0      0     0 ?        I<   22:54   0:00 [netns]
root          71  0.0  0.0      0     0 ?        I    22:54   0:01 [kworker/u4:2]
root         101  0.0  0.1  28288  9536 ?        Ss   22:54   0:01 /usr/lib/systemd/systemd-journald
201          293  0.2  0.0  13688  4068 ?        Ss   22:54   0:07 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile 
274          297  0.0  0.0  22520  4196 ?        Ss   22:54   0:00 /usr/lib/systemd/systemd-networkd
root         455  0.0  0.0      0     0 ?        I    22:54   0:00 [kworker/0:3]
root        1155  0.0  0.0   9540  3324 ?        Ss   22:54   0:00 bash /home/kubernetes/bin/health-monitor.sh container-runtime
root        1356  4.4  1.5 1396748 118236 ?      Ssl  22:56   2:30 /home/kubernetes/bin/kubelet --v=2 --cloud-provider=gce --experimental
root        1635  0.0  0.0 773444  6012 ?        Sl   22:56   0:00 containerd-shim -namespace moby -workdir /var/lib/containerd/io.contai
root        1660  0.1  0.4 417260 36292 ?        Ssl  22:56   0:03 kube-proxy --master=https://35.226.122.194 --kubeconfig=/var/lib/kube-
root        2019  0.0  0.1 107744  7872 ?        Ssl  22:56   0:00 /ip-masq-agent --masq-chain=IP-MASQ --nomasq-all-reserved-ranges
root        2171  0.0  0.0  16224  5020 ?        Ss   22:57   0:00 sshd: gke-1a5c3c1c4d5b7d80adbc [priv]
root        3203  0.0  0.0   1024     4 ?        Ss   22:57   0:00 /pause
root        5489  1.3  0.4  48008 34236 ?        Sl   22:57   0:43 calico-node -felix
root        6988  0.0  0.0  32648  5248 ?        Ss   23:01   0:00 nginx: master process nginx -g daemon off;
nginx       7009  0.0  0.0  33104  2584 ?        S    23:01   0:00 nginx: worker process

前面的输出显示了在nginx容器中运行的进程。在这些进程中有系统进程,如sshdkubeletkube-proxy等等。除了 Pod 使用主机 PID 命名空间外,您还可以向其他微服务的进程发送信号,比如向一个进程发送SIGKILL来终止它。

Linux 功能作为安全边界

Linux 功能是从传统的 Linux 权限检查演变而来的概念:特权和非特权。特权进程绕过所有内核权限检查。然后,Linux 将与 Linux 超级用户关联的特权划分为不同的单元- Linux 功能。有与网络相关的功能,比如CAP_NET_ADMINCAP_NET_BIND_SERVICECAP_NET_BROADCASTCAP_NET_RAW。还有审计相关的功能:CAP_AUDIT_CONTROLCAP_AUDIT_READCAP_AUDIT_WRITE。当然,还有类似管理员的功能:CAP_SYS_ADMIN

第四章中所述,在 Kubernetes 中应用最小特权原则,您可以为 Pod 中的容器配置 Linux 功能。默认情况下,以下是分配给 Kubernetes 集群中容器的功能列表:

  • CAP_SETPCAP

  • CAP_MKNOD

  • CAP_AUDIT_WRITE

  • CAP_CHOWN

  • CAP_NET_RAW

  • CAP_DAC_OVERRIDE

  • CAP_FOWNER

  • CAP_FSETID

  • CAP_KILL

  • CAP_SETGID

  • CAP_SETUID

  • CAP_NET_BIND_SERVICE

  • CAP_SYS_CHROOT

  • CAP_SETFCAP

对于大多数微服务来说,这些功能应该足以执行它们的日常任务。您应该放弃所有功能,只添加所需的功能。与主机命名空间类似,授予额外的功能可能会削弱其他微服务的安全边界。当您在容器中运行tcpdump命令时,以下是一个示例输出:

root@gke-demo-cluster-default-pool-c9e3510c-tfgh:/# tcpdump -i cali01fb9a4e4b4 -v
tcpdump: listening on cali01fb9a4e4b4, link-type EN10MB (Ethernet), capture size 262144 bytes
23:18:36.604766 IP (tos 0x0, ttl 64, id 27472, offset 0, flags [DF], proto UDP (17), length 86)
    10.56.1.14.37059 > 10.60.0.10.domain: 35359+ A? www.google.com.default.svc.cluster.local. (58)
23:18:36.604817 IP (tos 0x0, ttl 64, id 27473, offset 0, flags [DF], proto UDP (17), length 86)
    10.56.1.14.37059 > 10.60.0.10.domain: 35789+ AAAA? www.google.com.default.svc.cluster.local. (58)
23:18:36.606864 IP (tos 0x0, ttl 62, id 8294, offset 0, flags [DF], proto UDP (17), length 179)
    10.60.0.10.domain > 10.56.1.14.37059: 35789 NXDomain 0/1/0 (151)
23:18:36.606959 IP (tos 0x0, ttl 62, id 8295, offset 0, flags [DF], proto UDP (17), length 179)
    10.60.0.10.domain > 10.56.1.14.37059: 35359 NXDomain 0/1/0 (151)
23:18:36.607013 IP (tos 0x0, ttl 64, id 27474, offset 0, flags [DF], proto UDP (17), length 78)
    10.56.1.14.59177 > 10.60.0.10.domain: 7489+ A? www.google.com.svc.cluster.local. (50)
23:18:36.607053 IP (tos 0x0, ttl 64, id 27475, offset 0, flags [DF], proto UDP (17), length 78)
    10.56.1.14.59177 > 10.60.0.10.domain: 7915+ AAAA? www.google.com.svc.cluster.local. (50)

前面的输出显示,在容器内部,有tcpdump在网络接口cali01fb9a4e4b4上监听,该接口是为另一个 Pod 的网络通信创建的。通过授予主机网络命名空间和CAP_NET_ADMIN,您可以在容器内部从整个工作节点嗅探网络流量。一般来说,对容器授予的功能越少,对其他微服务的安全边界就越安全。

在系统层包装安全边界

默认情况下,为容器或 Pod 分配的专用 Linux 命名空间和有限的 Linux 功能为微服务建立了良好的安全边界。但是,用户仍然可以配置主机命名空间或为工作负载添加额外的 Linux 功能。这将削弱在同一工作节点上运行的其他微服务的安全边界。您应该非常小心地这样做。通常,监控工具或安全工具需要访问主机命名空间以执行其监控工作或检测工作。强烈建议使用PodSecurityPolicy来限制对主机命名空间以及额外功能的使用,以加强微服务的安全边界。

接下来,让我们从微服务的角度来看网络层设置的安全边界。

网络层的安全边界

Kubernetes 网络策略定义了不同组的 Pod 之间允许通信的规则。在前一章中,我们简要讨论了 Kubernetes 网络策略的出口规则,可以利用它来强制执行微服务的最小特权原则。在本节中,我们将更详细地介绍 Kubernetes 网络策略,并重点关注入口规则。我们将展示网络策略的入口规则如何帮助建立微服务之间的信任边界。

网络策略

如前一章所述,根据网络模型的要求,集群内的 Pod 可以相互通信。但从安全角度来看,您可能希望将您的微服务限制为只能被少数服务访问。我们如何在 Kubernetes 中实现这一点呢?让我们快速看一下以下 Kubernetes 网络策略示例:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

NetworkPolicy策略命名为test-network-policy。网络策略规范中值得一提的一些关键属性列在这里,以帮助您了解限制是什么:

  • podSelector:基于 Pod 标签,应用策略的 Pod 的分组。

  • Ingress:适用于顶层podSelector中指定的 Pod 的入口规则。Ingress下的不同元素如下所述:

  • ipBlock:允许与入口源进行通信的 IP CIDR 范围

  • namespaceSelector:基于命名空间标签,允许作为入口源的命名空间

  • podSelector:基于 Pod 标签,允许作为入口源的 Pod

  • ports:所有 Pod 应允许通信的端口和协议

  • 出口规则:适用于顶层podSelector中指定的 Pod 的出口规则。Ingress下的不同元素如下所述:
  • ipBlock:允许作为出口目的地进行通信的 IP CIDR 范围

  • namespaceSelector:基于命名空间标签,允许作为出口目的地的命名空间

  • podSelector:基于 Pod 标签,允许作为出口目的地的 Pod

  • ports:所有 Pod 应允许通信的目标端口和协议

通常,ipBlock用于指定允许在 Kubernetes 集群中与微服务交互的外部 IP 块,而命名空间选择器和 Pod 选择器用于限制在同一 Kubernetes 集群中微服务之间的网络通信。

为了从网络方面加强微服务的信任边界,您可能希望要么指定来自外部的允许的ipBlock,要么允许来自特定命名空间的微服务。以下是另一个示例,通过使用namespaceSelectorpodSelector来限制来自特定 Pod 和命名空间的入口源:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-good
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          from: good
      podSelector:
        matchLabels:
          from: good

请注意,在podSelector属性前面没有-。这意味着入口源只能是具有标签from: good的命名空间中的 Pod。这个网络策略保护了默认命名空间中具有标签app: web的 Pod:

图 5.1 - 通过 Pod 和命名空间标签限制传入流量的网络策略

图 5.1 - 通过 Pod 和命名空间标签限制传入流量的网络策略

在前面的图表中,good命名空间具有标签from: good,而bad命名空间具有标签from: bad。它说明只有在具有标签from: good的命名空间中的 Pod 才能访问默认命名空间中的nginx-web服务。其他 Pod,无论它们是否来自good命名空间但没有标签from: good,或者来自其他命名空间,都无法访问默认命名空间中的nginx-web服务。

摘要

在本章中,我们讨论了安全边界的重要性。了解 Kubernetes 生态系统中的安全域和安全边界有助于管理员了解攻击的影响范围,并制定限制攻击造成的损害的缓解策略。了解 Kubernetes 实体是巩固安全边界的起点。了解系统层中构建的安全边界与 Linux 命名空间和功能的能力是下一步。最后但同样重要的是,了解网络策略的威力也是构建安全细分到微服务中的关键。

在这一章中,您应该掌握安全领域和安全边界的概念。您还应该了解 Kubernetes 中的安全领域、常见实体,以及在 Kubernetes 实体内部或周围构建的安全边界。您应该知道使用内置安全功能(如 PodSecurityPolicy 和 NetworkPolicy)来加固安全边界,并仔细配置工作负载的安全上下文的重要性。

在下一章中,我们将讨论如何保护 Kubernetes 组件的安全。特别是,有一些配置细节需要您注意。

问题

  1. Kubernetes 中的安全领域是什么?

  2. 您与哪些常见的 Kubernetes 实体进行交互?

  3. 如何限制 Kubernetes 用户访问特定命名空间中的对象?

  4. 启用 hostPID 对于 Pod 意味着什么?

  5. 尝试配置网络策略以保护您的服务,只允许特定的 Pod 作为入口源。

进一步参考

第二部分:保护 Kubernetes 部署和集群

在本节中,您将通过实际操作学习如何以两种方式保护 Kubernetes 部署/集群:您将学习如何在构建、部署和运行时阶段保护 DevOps 流水线,并且您将了解深度防御,包括合规性、配置、身份验证、授权、资源管理、日志记录和监控、检测以及事件响应。

本节包括以下章节:

  • 第六章,保护集群组件

  • 第七章,认证、授权和准入控制

  • 第八章,保护 Kubernetes Pod

  • 第九章,DevOps 流水线中的图像扫描

  • 第十章,实时监控和资源管理 Kubernetes 集群

  • 第十一章,深度防御

第六章:保护集群组件

在之前的章节中,我们看过了 Kubernetes 集群的架构。一个 Kubernetes 集群包括主要组件,包括kube-apiserveretcdkube-scheduler、CoreDNS、kube-controller-managercloud-controller-manager,以及节点组件,包括kubeletkube-proxycontainer-runtime。主要组件负责集群管理,它们构成了集群的控制平面。另一方面,节点组件负责节点上 pod 和容器的运行。

第三章中,威胁建模,我们简要讨论了 Kubernetes 集群中的组件需要进行配置以确保集群的安全。任何集群组件的妥协都可能导致数据泄露。环境的错误配置是传统或微服务环境中数据泄露的主要原因之一。了解每个组件的配置以及每个设置如何打开新的攻击面是很重要的。因此,集群管理员了解不同的配置是很重要的。

在本章中,我们将详细讨论如何保护集群中的每个组件。在许多情况下,可能无法遵循所有安全最佳实践,但重要的是要强调风险,并制定一套缓解策略,以防攻击者试图利用易受攻击的配置。

对于每个主要和节点组件,我们简要讨论了 Kubernetes 集群中具有安全相关配置的组件的功能,并详细查看了每个配置。我们查看了这些配置的可能设置,并强调了推荐的最佳实践。最后,我们介绍了kube-bench,并演示了如何使用它来评估您集群的安全姿势。

在本章中,我们将涵盖以下主题:

  • 保护 kube-apiserver

  • 保护 kubelet

  • 保护 etcd

  • 保护 kube-scheduler

  • 保护 kube-controller-manager

  • 保护 CoreDNS

  • 对集群的安全配置进行基准测试

保护 kube-apiserver

kube-apiserver是您集群的网关。它实现了表述状态转移REST应用程序编程接口API)来授权和验证对象的请求。它是与 Kubernetes 集群内的其他组件进行通信和管理的中央网关。它执行三个主要功能:

  • API 管理kube-apiserver公开用于集群管理的 API。开发人员和集群管理员使用这些 API 来修改集群的状态。

  • 请求处理:对于对象管理和集群管理的请求进行验证和处理。

  • 内部消息传递:API 服务器与集群中的其他组件进行交互,以确保集群正常运行。

API 服务器的请求在处理之前经过以下步骤:

  1. 身份验证kube-apiserver首先验证请求的来源。kube-apiserver支持多种身份验证模式,包括客户端证书、持有者令牌和超文本传输协议HTTP)身份验证。

  2. 授权:一旦验证了请求来源的身份,API 服务器会验证该来源是否被允许执行请求。kube-apiserver默认支持基于属性的访问控制ABAC)、基于角色的访问控制RBAC)、节点授权和用于授权的 Webhooks。RBAC 是推荐的授权模式。

  3. 准入控制器:一旦kube-apiserver验证并授权请求,准入控制器会解析请求,以检查其是否在集群内允许。如果任何准入控制器拒绝请求,则该请求将被丢弃。

kube-apiserver是集群的大脑。API 服务器的妥协会导致集群的妥协,因此确保 API 服务器安全至关重要。Kubernetes 提供了大量设置来配置 API 服务器。让我们接下来看一些与安全相关的配置。

为了保护 API 服务器,您应该执行以下操作:

  • 禁用匿名身份验证:使用anonymous-auth=false标志将匿名身份验证设置为false。这可以确保被所有身份验证模块拒绝的请求不被视为匿名并被丢弃。

  • 禁用基本身份验证:基本身份验证在kube-apiserver中为方便起见而受支持,不应使用。基本身份验证密码会持续存在。kube-apiserver使用--basic-auth-file参数来启用基本身份验证。确保不使用此参数。

  • 禁用令牌认证--token-auth-file启用集群的基于令牌的认证。不建议使用基于令牌的认证。静态令牌会永久存在,并且需要重新启动 API 服务器才能更新。应该使用客户端证书进行认证。

  • 确保与 kubelet 的连接使用 HTTPS:默认情况下,--kubelet-https设置为true。确保对于kube-apiserver,不要将此参数设置为false

  • 禁用分析:使用--profiling启用分析会暴露不必要的系统和程序细节。除非遇到性能问题,否则通过设置--profiling=false来禁用分析。

  • 禁用 AlwaysAdmit--enable-admission-plugins可用于启用默认未启用的准入控制插件。AlwaysAdmit接受请求。确保该插件不在--enabled-admission-plugins列表中。

  • 使用 AlwaysPullImagesAlwaysPullImages准入控制确保节点上的镜像在没有正确凭据的情况下无法使用。这可以防止恶意 Pod 为节点上已存在的镜像创建容器。

  • 使用 SecurityContextDeny:如果未启用PodSecurityPolicy,应使用此准入控制器。SecurityContextDeny确保 Pod 无法修改SecurityContext以提升特权。

  • 启用审计:审计在kube-apiserver中默认启用。确保--audit-log-path设置为安全位置的文件。此外,确保审计的maxagemaxsizemaxbackup参数设置满足合规性要求。

  • 禁用 AlwaysAllow 授权:授权模式确保具有正确权限的用户的请求由 API 服务器解析。不要在--authorization-mode中使用AlwaysAllow

  • 启用 RBAC 授权:RBAC 是 API 服务器的推荐授权模式。ABAC 难以使用和管理。RBAC 角色和角色绑定的易用性和易于更新使其适用于经常扩展的环境。

  • 确保对 kubelet 的请求使用有效证书:默认情况下,kube-apiserverkubelet的请求使用 HTTPS。启用--kubelet-certificate-authority--kubelet-client-key--kubelet-client-key确保通信使用有效的 HTTPS 证书。

  • 启用 service-account-lookup:除了确保服务账户令牌有效外,kube-apiserver还应验证令牌是否存在于etcd中。确保--service-account-lookup未设置为false

  • 启用 PodSecurityPolicy--enable-admission-plugins可用于启用PodSecurityPolicy。正如我们在[第五章](B15566_05_Final_ASB_ePub.xhtml#_idTextAnchor144)中所看到的,配置 Kubernetes 安全边界PodSecurityPolicy用于定义 pod 的安全敏感标准。我们将在[第八章](B15566_08_Final_ASB_ePub.xhtml#_idTextAnchor249)中深入探讨创建 pod 安全策略。

  • 使用服务账户密钥文件:使用--service-account-key-file可以启用对服务账户密钥的轮换。如果未指定此选项,kube-apiserver将使用传输层安全性TLS)证书的私钥来签署服务账户令牌。

  • 启用对 etcd 的授权请求--etcd-certfile--etcd-keyfile可用于标识对etcd的请求。这可以确保etcd可以拒绝任何未经识别的请求。

  • 不要禁用 ServiceAccount 准入控制器:此准入控制自动化服务账户。启用ServiceAccount可确保可以将具有受限权限的自定义ServiceAccount与不同的 Kubernetes 对象一起使用。

  • 不要为请求使用自签名证书:如果为kube-apiserver启用了 HTTPS,则应提供--tls-cert-file--tls-private-key-file,以确保不使用自签名证书。

  • 连接到 etcd 的安全连接:设置--etcd-cafile允许kube-apiserver使用证书文件通过安全套接字层SSL)向etcd验证自身。

  • 使用安全的 TLS 连接:将--tls-cipher-suites设置为仅使用强密码。--tls-min-version用于设置最低支持的 TLS 版本。TLS 1.2 是推荐的最低版本。

  • 启用高级审计:通过将--feature-gates设置为AdvancedAuditing=false可以禁用高级审计。确保此字段存在并设置为true。高级审计有助于调查是否发生违规行为。

在 Minikube 上,kube-apiserver的配置如下:

$ps aux | grep kube-api
root      4016  6.1 17.2 495148 342896 ?       Ssl  01:03   0:16 kube-apiserver --advertise-address=192.168.99.100 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota --enable-bootstrap-token-auth=true --etcd-cafile=/var/lib/minikube/certs/etcd/ca.crt --etcd-certfile=/var/lib/minikube/certs/apiserver-etcd-client.crt --etcd-keyfile=/var/lib/minikube/certs/apiserver-etcd-client.key --etcd-servers=https://127.0.0.1:2379 --insecure-port=0 --kubelet-client-certificate=/var/lib/minikube/certs/apiserver-kubelet-client.crt --kubelet-client-key=/var/lib/minikube/certs/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/var/lib/minikube/certs/front-proxy-client.crt --proxy-client-key-file=/var/lib/minikube/certs/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/var/lib/minikube/certs/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=8443 --service-account-key-file=/var/lib/minikube/certs/sa.pub --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/var/lib/minikube/certs/apiserver.crt --tls-private-key-file=/var/lib/minikube/certs/apiserver.key

正如您所看到的,默认情况下,在 Minikube 上,kube-apiserver并未遵循所有安全最佳实践。例如,默认情况下未启用PodSecurityPolicy,也未设置强密码套件和tls最低版本。集群管理员有责任确保 API 服务器的安全配置。

保护 kubelet

kubelet是 Kubernetes 的节点代理。它管理 Kubernetes 集群中对象的生命周期,并确保节点上的对象处于健康状态。

要保护kubelet,您应该执行以下操作:

  • 禁用匿名身份验证:如果启用了匿名身份验证,则被其他身份验证方法拒绝的请求将被视为匿名。确保为每个kubelet实例设置--anonymous-auth=false

  • 设置授权模式:使用配置文件设置kubelet的授权模式。可以使用--config参数指定配置文件。确保授权模式列表中没有AlwaysAllow

  • 轮换 kubelet 证书:可以使用kubelet配置文件中的RotateCertificates配置来轮换kubelet证书。这应与RotateKubeletServerCertificate一起使用,以自动请求轮换服务器证书。

  • 提供证书颁发机构(CA)包kubelet使用 CA 包来验证客户端证书。可以使用配置文件中的ClientCAFile参数进行设置。

  • 禁用只读端口:默认情况下,kubelet启用了只读端口,应该禁用。只读端口没有身份验证或授权。

  • 启用 NodeRestriction 准入控制器NodeRestriction准入控制器仅允许kubelet修改其绑定的节点上的节点和 Pod 对象。

  • 限制对 Kubelet API 的访问:只有kube-apiserver组件与kubelet API 交互。如果尝试在节点上与kubelet API 通信,将被禁止。这是通过为kubelet使用 RBAC 来确保的。

在 Minikube 上,kubelet配置如下:

root      4286  2.6  4.6 1345544 92420 ?       Ssl  01:03   0:18 /var/lib/minikube/binaries/v1.17.3/kubelet --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-domain=cluster.local --config=/var/lib/kubelet/config.yaml --container-runtime=docker --fail-swap-on=false --hostname-override=minikube --kubeconfig=/etc/kubernetes/kubelet.conf --node-ip=192.168.99.100 --pod-manifest-path=/etc/kubernetes/manifests

与 API 服务器类似,默认情况下,kubelet上并非所有安全配置都被使用,例如禁用只读端口。接下来,我们将讨论集群管理员如何保护etcd

保护 etcd

etcd是 Kubernetes 用于数据存储的键值存储。它存储了 Kubernetes 集群的状态、配置和秘密。只有kube-apiserver应该可以访问etcdetcd的泄露可能导致集群泄露。

为了保护etcd,您应该执行以下操作:

  • 限制节点访问:使用 Linux 防火墙确保只允许需要访问etcd的节点访问。

  • 确保 API 服务器使用 TLS--cert-file--key-file确保对etcd的请求是安全的。

  • 使用有效证书--client-cert-auth确保客户端通信使用有效证书,并将--auto-tls设置为false确保不使用自签名证书。

  • 加密静态数据:将--encryption-provider-config传递给 API 服务器,以确保在etcd中对静态数据进行加密。

在 Minikube 上,etcd配置如下:

$ ps aux | grep etcd
root      3992  1.9  2.4 10612080 48680 ?      Ssl  01:03   0:18 etcd --advertise-client-urls=https://192.168.99.100:2379 --cert-file=/var/lib/minikube/certs/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/minikube/etcd --initial-advertise-peer-urls=https://192.168.99.100:2380 --initial-cluster=minikube=https://192.168.99.100:2380 --key-file=/var/lib/minikube/certs/etcd/server.key --listen-client-urls=https://127.0.0.1:2379,https://192.168.99.100:2379 --listen-metrics-urls=http://127.0.0.1:2381 --listen-peer-urls=https://192.168.99.100:2380 --name=minikube --peer-cert-file=/var/lib/minikube/certs/etcd/peer.crt --peer-client-cert-auth=true --peer-key-file=/var/lib/minikube/certs/etcd/peer.key --peer-trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt --snapshot-count=10000 --trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt

etcd存储着 Kubernetes 集群的敏感数据,如私钥和秘密。etcd的泄露就意味着api-server组件的泄露。集群管理员在设置etcd时应特别注意。

保护 kube-scheduler

接下来,我们来看看kube-scheduler。正如我们在第一章中已经讨论过的,Kubernetes 架构kube-scheduler负责为 pod 分配节点。一旦 pod 分配给节点,kubelet就会执行该 pod。kube-scheduler首先过滤可以运行 pod 的节点集,然后根据每个节点的评分,将 pod 分配给评分最高的过滤节点。kube-scheduler组件的泄露会影响集群中 pod 的性能和可用性。

为了保护kube-scheduler,您应该执行以下操作:

  • 禁用分析:对kube-scheduler的分析会暴露系统细节。将--profiling设置为false可以减少攻击面。

  • 禁用 kube-scheduler 的外部连接:应禁用kube-scheduler的外部连接。将AllowExtTrafficLocalEndpoints设置为true会启用kube-scheduler的外部连接。确保使用--feature-gates禁用此功能。

  • 启用 AppArmor:默认情况下,kube-scheduler启用了AppArmor。确保不要禁用kube-schedulerAppArmor

在 Minikube 上,kube-scheduler配置如下:

$ps aux | grep kube-scheduler
root      3939  0.5  2.0 144308 41640 ?        Ssl  01:03   0:02 kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=0.0.0.0 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true

kube-apiserver类似,调度程序也没有遵循所有的安全最佳实践,比如禁用分析。

保护 kube-controller-manager

kube-controller-manager管理集群的控制循环。它通过 API 服务器监视集群的更改,并旨在将集群从当前状态移动到期望的状态。kube-controller-manager默认提供多个控制器管理器,如复制控制器和命名空间控制器。对kube-controller-manager的妥协可能导致对集群的更新被拒绝。

要保护kube-controller-manager,您应该使用--use-service-account-credentials,与 RBAC 一起使用可以确保控制循环以最低特权运行。

在 Minikube 上,kube-controller-manager的配置如下:

$ps aux | grep kube-controller-manager
root      3927  1.8  4.5 209520 90072 ?        Ssl  01:03   0:11 kube-controller-manager --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=0.0.0.0 --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-signing-cert-file=/var/lib/minikube/certs/ca.crt --cluster-signing-key-file=/var/lib/minikube/certs/ca.key --controllers=*,bootstrapsigner,tokencleaner --kubeconfig=/etc/kubernetes/controller-manager.conf --leader-elect=true --requestheader-client-ca-file=/var/lib/minikube/certs/front-proxy-ca.crt --root-ca-file=/var/lib/minikube/certs/ca.crt --service-account-private-key-file=/var/lib/minikube/certs/sa.key --use-service-account-credentials=true

接下来,让我们谈谈如何保护 CoreDNS。

保护 CoreDNS

kube-dns是 Kubernetes 集群的默认域名系统DNS)服务器。DNS 服务器帮助内部对象(如服务、pod 和容器)相互定位。kube-dns由三个容器组成,详细如下:

  • kube-dns:此容器使用 SkyDNS 执行 DNS 解析服务。

  • dnsmasq:轻量级 DNS 解析器。它从 SkyDNS 缓存响应。

  • sidecar:这个监视健康并处理 DNS 的度量报告。

自 1.11 版本以来,kube-dns已被 CoreDNS 取代,因为 dnsmasq 存在安全漏洞,SkyDNS 存在性能问题。CoreDNS 是一个单一容器,提供了kube-dns的所有功能。

要编辑 CoreDNS 的配置文件,您可以使用kubectl,就像这样:

$ kubectl -n kube-system edit configmap coredns

在 Minikube 上,默认的 CoreDNS 配置文件如下:

# Please edit the object below. Lines beginning with a '#' 
# will be ignored, and an empty file will abort the edit. 
# If an error occurs while saving this file will be
# reopened with the relevant failures.
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

要保护 CoreDNS,请执行以下操作:

  • 确保健康插件未被禁用health插件监视 CoreDNS 的状态。它用于确认 CoreDNS 是否正在运行。通过在Corefile中添加health来启用它。

  • 为 CoreDNS 启用 istioistio是 Kubernetes 使用的服务网格,用于提供服务发现、负载平衡和认证。它在 Kubernetes 中默认不可用,需要作为外部依赖项添加。您可以通过启动istio服务并将istio服务的代理添加到配置文件中来向集群添加istio,就像这样:

global:53 {
         errors
         proxy . {cluster IP of this istio-core-dns service}
    }

现在我们已经查看了集群组件的不同配置,重要的是要意识到随着组件变得更加复杂,将会添加更多的配置参数。集群管理员不可能记住这些配置。因此,接下来,我们将讨论一种帮助集群管理员监视集群组件安全状况的工具。

对集群安全配置进行基准测试

互联网安全中心CIS)发布了一份 Kubernetes 基准,可以供集群管理员使用,以确保集群遵循推荐的安全配置。发布的 Kubernetes 基准超过 200 页。

kube-bench是一个用 Go 编写并由 Aqua Security 发布的自动化工具,运行 CIS 基准中记录的测试。这些测试是用YAML Ain't Markup LanguageYAML)编写的,使其易于演变。

kube-bench可以直接在节点上使用kube-bench二进制文件运行,如下所示:

$kube-bench node --benchmark cis-1.4

对于托管在gkeeksaks上的集群,kube-bench作为一个 pod 运行。一旦 pod 运行完成,您可以查看日志以查看结果,如下面的代码块所示:

$ kubectl apply -f job-gke.yaml
$ kubectl get pods
NAME               READY   STATUS      RESTARTS   AGE
kube-bench-2plpm   0/1     Completed   0          5m20s
$ kubectl logs kube-bench-2plpm
[INFO] 4 Worker Node Security Configuration
[INFO] 4.1 Worker Node Configuration Files
[WARN] 4.1.1 Ensure that the kubelet service file permissions are set to 644 or more restrictive (Not Scored)
[WARN] 4.1.2 Ensure that the kubelet service file ownership is set to root:root (Not Scored)
[PASS] 4.1.3 Ensure that the proxy kubeconfig file permissions are set to 644 or more restrictive (Scored)
[PASS] 4.1.4 Ensure that the proxy kubeconfig file ownership is set to root:root (Scored)
[WARN] 4.1.5 Ensure that the kubelet.conf file permissions are set to 644 or more restrictive (Not Scored)
[WARN] 4.1.6 Ensure that the kubelet.conf file ownership is set to root:root (Not Scored)
[WARN] 4.1.7 Ensure that the certificate authorities file permissions are set to 644 or more restrictive (Not Scored)
......
== Summary ==
0 checks PASS
0 checks FAIL
37 checks WARN
0 checks INFO

重要的是要调查具有FAIL状态的检查。您应该力求没有失败的检查。如果由于任何原因这是不可能的,您应该制定一个针对失败检查的风险缓解计划。

kube-bench是一个有用的工具,用于监视遵循安全最佳实践的集群组件。建议根据自己的环境添加/修改kube-bench规则。大多数开发人员在启动新集群时运行kube-bench,但定期运行它以监视集群组件是否安全很重要。

总结

在本章中,我们查看了每个主节点和节点组件的不同安全敏感配置:kube-apiserverkube-schedulerkube-controller-managerkubelet、CoreDNS 和etcd。我们了解了如何保护每个组件。默认情况下,组件可能不遵循所有安全最佳实践,因此集群管理员有责任确保组件是安全的。最后,我们看了一下kube-bench,它可以用来了解正在运行的集群的安全基线。

重要的是要了解这些配置,并确保组件遵循这些检查表,以减少受到威胁的机会。

在下一章中,我们将介绍 Kubernetes 中的身份验证和授权机制。在本章中,我们简要讨论了一些准入控制器。我们将深入探讨不同的准入控制器,并最终讨论它们如何被利用以提供更精细的访问控制。

问题

  1. 什么是基于令牌的身份验证?

  2. 什么是NodeRestriction准入控制器?

  3. 如何确保数据在etcd中处于加密状态?

  4. 为什么 CoreDNS 取代了kube-dns

  5. 如何在弹性 Kubernetes 服务EKS)集群上使用kube-bench

进一步阅读

您可以参考以下链接,了解本章涵盖的主题的更多信息:

第七章:身份验证、授权和准入控制

身份验证和授权在保护应用程序中起着非常重要的作用。这两个术语经常被交替使用,但它们是非常不同的。身份验证验证用户的身份。一旦身份得到验证,授权就用来检查用户是否有执行所需操作的特权。身份验证使用用户知道的东西来验证他们的身份;在最简单的形式中,这是用户名和密码。一旦应用程序验证了用户的身份,它会检查用户可以访问哪些资源。在大多数情况下,这是访问控制列表的一个变体。用户的访问控制列表与请求属性进行比较,以允许或拒绝操作。

在本章中,我们将讨论请求在被kube-apiserver处理之前如何经过身份验证、授权模块和准入控制器的处理。我们将详细介绍不同模块和准入控制器的细节,并强调推荐的安全配置。

最后,我们将介绍Open Policy AgentOPA),这是一个开源工具,可用于在微服务中实现授权。在 Kubernetes 中,我们将看看它如何作为一个验证准入控制器。许多集群需要比 Kubernetes 已提供的更细粒度的授权。使用 OPA,开发人员可以定义可以在运行时更新的自定义授权策略。有几个利用 OPA 的开源工具,比如 Istio。

在本章中,我们将讨论以下主题:

  • 在 Kubernetes 中请求工作流

  • Kubernetes 身份验证

  • Kubernetes 授权

  • 准入控制器

  • 介绍 OPA

在 Kubernetes 中请求工作流

在 Kubernetes 中,kube-apiserver处理所有修改集群状态的请求。kube-apiserver首先验证请求的来源。它可以使用一个或多个身份验证模块,包括客户端证书、密码或令牌。请求依次从一个模块传递到另一个模块。如果请求没有被所有模块拒绝,它将被标记为匿名请求。API 服务器可以配置为允许匿名请求。

一旦请求的来源得到验证,它将通过授权模块,检查请求的来源是否被允许执行操作。授权模块允许请求,如果策略允许用户执行操作。Kubernetes 支持多个授权模块,如基于属性的访问控制(ABAC)、基于角色的访问控制(RBAC)和 webhooks。与认证模块类似,集群可以使用多个授权:

图 7.1 - 在 kube-apiserver 处理之前进行请求解析

图 7.1 - 在 kube-apiserver 处理之前进行请求解析

经过授权和认证模块后,准入控制器修改或拒绝请求。准入控制器拦截创建、更新或删除对象的请求。准入控制器分为两类:变异或验证。变异准入控制器首先运行;它们修改它们承认的请求。接下来运行验证准入控制器。这些控制器不能修改对象。如果任何准入控制器拒绝请求,将向用户返回错误,并且请求将不会被 API 服务器处理。

Kubernetes 认证

Kubernetes 中的所有请求都来自外部用户、服务账户或 Kubernetes 组件。如果请求的来源未知,则被视为匿名请求。根据组件的配置,认证模块可以允许或拒绝匿名请求。在 v1.6+中,匿名访问被允许以支持匿名和未经认证的用户,用于 RBAC 和 ABAC 授权模式。可以通过向 API 服务器配置传递--anonymous-auth=false标志来明确禁用匿名访问:

$ps aux | grep api
root      3701  6.1  8.7 497408 346244 ?       Ssl  21:06   0:16 kube-apiserver --advertise-address=192.168.99.111 --allow-privileged=true --anonymous-auth=false

Kubernetes 使用一个或多个这些认证策略。让我们逐一讨论它们。

客户端证书

在 Kubernetes 中,使用 X509 证书颁发机构(CA)证书是最常见的认证策略。可以通过向服务器传递--client-ca-file=file_path来启用它。传递给 API 服务器的文件包含 CA 的列表,用于在集群中创建和验证客户端证书。证书中的“通用名称”属性通常用作请求的用户名,“组织”属性用于标识用户的组:

kube-apiserver --advertise-address=192.168.99.104 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt

要创建新证书,需要执行以下步骤:

  1. 生成私钥。可以使用openssleasyrsacfssl生成私钥:
openssl genrsa -out priv.key 4096
  1. 生成证书签名请求CSR)。使用私钥和类似以下的配置文件生成 CSR。此 CSR 是为test用户生成的,该用户将成为dev组的一部分:
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[ dn ]
CN = test
O = dev
[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment extendedKeyUsage=serverAuth,clientAuth

您可以使用openssl生成 CSR:

openssl req -config ./csr.cnf -new -key priv.key -nodes -out new.csr
  1. 签署 CSR。使用以下 YAML 文件创建一个 KubernetesCertificateSigningRequest请求:
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
 name: mycsr
spec:
 groups:
 - system:authenticated
 request: ${BASE64_CSR}
 usages:
 - digital signature
 - key encipherment
 - server auth
 - client auth

之前生成的证书签名请求与前面的 YAML 规范一起使用,生成一个新的 Kubernetes 证书签名请求:

$ export BASE64_CSR=$(cat ./new.csr | base64 | tr -d '\n')
$ cat csr.yaml | envsubst | kubectl apply -f -

创建此请求后,需要由集群管理员批准以生成证书:

kubectl certificate approve mycsr
  1. 导出 CRT。可以使用kubectl导出证书:
kubectl get csr mycsr -o jsonpath='{.status.certificate}' \
 | base64 --decode > new.crt

接下来,我们将看一下静态令牌,这是开发和调试环境中常用的身份验证模式,但不应在生产集群中使用。

静态令牌

API 服务器使用静态文件来读取令牌。将此静态文件传递给 API 服务器使用--token-auth-file=<path>。令牌文件是一个逗号分隔的文件,包括secretuseruidgroup1group2

令牌作为 HTTP 标头传递在请求中:

Authorization: Bearer 66e6a781-09cb-4e7e-8e13-34d78cb0dab6

令牌会持久存在,API 服务器需要重新启动以更新令牌。这是一种推荐的身份验证策略。如果攻击者能够在集群中生成恶意 Pod,这些令牌很容易被破坏。一旦被破坏,生成新令牌的唯一方法是重新启动 API 服务器。

接下来,我们将看一下基本身份验证,这是静态令牌的一种变体,多年来一直作为 Web 服务的身份验证方法。

基本身份验证

与静态令牌类似,Kubernetes 还支持基本身份验证。可以通过使用basic-auth-file=<path>来启用。认证凭据存储在 CSV 文件中,包括passworduseruidgroup1group2

用户名和密码作为认证标头传递在请求中:

Authentication: Basic base64(user:password)

与静态令牌类似,基本身份验证密码无法在不重新启动 API 服务器的情况下更改。不应在生产集群中使用基本身份验证。

引导令牌

引导令牌是静态令牌的一种改进。引导令牌是 Kubernetes 中默认使用的身份验证方法。它们是动态管理的,并存储为kube-system中的秘密。要启用引导令牌,请执行以下操作:

  1. 在 API 服务器中使用--enable-bootstrap-token-auth来启用引导令牌验证器:
$ps aux | grep api
root      3701  3.8  8.8 497920 347140 ?       Ssl  21:06   4:58 kube-apiserver --advertise-address=192.168.99.111 --allow-privileged=true --anonymous-auth=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota --enable-bootstrap-token-auth=true
  1. 使用controller标志在控制器管理器中启用tokencleaner
$ ps aux | grep controller
root      3693  1.4  2.3 211196 94396 ?        Ssl  21:06   1:55 kube-controller-manager --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=127.0.0.1 --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-name=mk --cluster-signing-cert-file=/var/lib/minikube/certs/ca.crt --cluster-signing-key-file=/var/lib/minikube/certs/ca.key --controllers=*,bootstrapsigner,tokencleaner
  1. 与令牌身份验证类似,引导令牌作为请求中的 HTTP 头传递:
Authorization: Bearer 123456.aa1234fdeffeeedf

令牌的第一部分是TokenId值,第二部分是TokenSecret值。TokenController确保从系统秘密中删除过期的令牌。

服务账户令牌

服务账户验证器会自动启用。它验证签名的持有者令牌。签名密钥是使用--service-account-key-file指定的。如果未指定此值,则将使用 Kube API 服务器的私钥:

$ps aux | grep api
root      3711 27.1 14.9 426728 296552 ?       Ssl  04:22   0:04 kube-apiserver --advertise-address=192.168.99.104 ... --secure-port=8443 --service-account-key-file=/var/lib/minikube/certs/sa.pub --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/var/lib/minikube/certs/apiserver.crt --tls-private-key-file=/var/lib/minikube/certs/apiserver.key
docker    4496  0.0  0.0  11408   544 pts/0    S+   04:22   0:00 grep api

服务账户由kube-apiserver创建,并与 pod 关联。这类似于 AWS 中的实例配置文件。如果未指定服务账户,则默认服务账户将与 pod 关联。

要创建一个名为 test 的服务账户,您可以使用以下命令:

kubectl create serviceaccount test 

服务账户有关联的秘密,其中包括 API 服务器的 CA 和签名令牌:

$ kubectl get serviceaccounts test -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2020-03-29T04:35:58Z"
  name: test
  namespace: default
  resourceVersion: "954754"
  selfLink: /api/v1/namespaces/default/serviceaccounts/test
  uid: 026466f3-e2e8-4b26-994d-ee473b2f36cd
secrets:
- name: test-token-sdq2d

如果我们列举细节,我们可以看到证书和令牌:

$ kubectl get secret test-token-sdq2d -o yaml
apiVersion: v1
data:
  ca.crt: base64(crt)
  namespace: ZGVmYXVsdA==
  token: base64(token)
kind: Secret

接下来,我们将讨论 webhook 令牌。一些企业拥有远程身份验证和授权服务器,通常在所有服务中使用。在 Kubernetes 中,开发人员可以使用 webhook 令牌来利用远程服务进行身份验证。

Webhook 令牌

在 webhook 模式下,Kubernetes 会调用集群外的 REST API 来确定用户的身份。可以通过向 API 服务器传递--authorization-webhook-config-file=<path>来启用身份验证的 webhook 模式。

以下是 webhook 配置的示例。在此示例中,authn.example.com/authenticate用作 Kubernetes 集群的身份验证端点:

clusters:
  - name: name-of-remote-authn-service
    cluster:
      certificate-authority: /path/to/ca.pem
      server: https://authn.example.com/authenticate

让我们看看另一种远程服务可以用于身份验证的方式。

身份验证代理

kube-apiserver可以配置为使用X-Remote请求头标识用户。您可以通过向 API 服务器添加以下参数来启用此方法:

--requestheader-username-headers=X-Remote-User
--requestheader-group-headers=X-Remote-Group
--requestheader-extra-headers-prefix=X-Remote-Extra-

每个请求都有以下标头来识别它们:

GET / HTTP/1.1
X-Remote-User: foo
X-Remote-Group: bar
X-Remote-Extra-Scopes: profile

API 代理使用 CA 验证请求。

用户冒充

集群管理员和开发人员可以使用用户冒充来调试新用户的身份验证和授权策略。要使用用户冒充,用户必须被授予冒充特权。API 服务器使用以下标头来冒充用户:

  • 冒充-用户

  • 冒充-组

  • 冒充-额外-*

一旦 API 服务器接收到冒充标头,API 服务器会验证用户是否经过身份验证并具有冒充特权。如果是,则请求将以冒充用户的身份执行。kubectl可以使用--as--as-group标志来冒充用户:

kubectl apply -f pod.yaml --as=dev-user --as-group=system:dev

一旦身份验证模块验证了用户的身份,它们会解析请求以检查用户是否被允许访问或修改请求。

Kubernetes 授权

授权确定请求是否允许或拒绝。一旦确定请求的来源,活动授权模块会评估请求的属性与用户的授权策略,以允许或拒绝请求。每个请求依次通过授权模块,如果任何模块提供允许或拒绝的决定,它将自动被接受或拒绝。

请求属性

授权模块解析请求中的一组属性,以确定请求是否应该被解析、允许或拒绝:

  • 用户:请求的发起者。这在身份验证期间进行验证。

  • :用户所属的组。这是在身份验证层中提供的。

  • API:请求的目的地。

  • 请求动词:请求的类型,可以是GETCREATEPATCHDELETE等。

  • 资源:正在访问的资源的 ID 或名称。

  • 命名空间:正在访问的资源的命名空间。

  • 请求路径:如果请求是针对非资源端点的,则使用路径来检查用户是否被允许访问端点。这对于apihealthz端点是正确的。

现在,让我们看看使用这些请求属性来确定请求发起者是否被允许发起请求的不同授权模式。

授权模式

现在,让我们看看 Kubernetes 中可用的不同授权模式。

节点

节点授权模式授予 kubelet 访问节点的服务、端点、节点、Pod、秘密和持久卷的权限。kubelet 被识别为system:nodes组的一部分,用户名为system:node:<name>,由节点授权者授权。这种模式在 Kubernetes 中默认启用。

NodeRestriction准入控制器与节点授权者一起使用,我们将在本章后面学习,以确保 kubelet 只能修改其正在运行的节点上的对象。API 服务器使用--authorization-mode=Node标志来使用节点授权模块:

$ps aux | grep api
root      3701  6.1  8.7 497408 346244 ?       Ssl  21:06   0:16 kube-apiserver --advertise-address=192.168.99.111 --allow-privileged=true --anonymous-auth=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota

节点授权与 ABAC 或 RBAC 一起使用,接下来我们将看一下。

ABAC

使用 ABAC,通过验证请求的属性来允许请求。可以通过在 API 服务器中使用--authorization-policy-file=<path>--authorization-mode=ABAC来启用 ABAC 授权模式。

策略包括每行一个 JSON 对象。每个策略包括以下内容:

  • 版本:策略格式的 API 版本。

  • 种类Policy字符串用于策略。

  • 规范:包括用户、组和资源属性,如apiGroupnamespacenonResourcePath(如/version/apisreadonly),以允许不修改资源的请求。

一个示例策略如下:

{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "pods", "readonly": true}} 

此策略允许 kubelet 读取任何 Pod。ABAC 难以配置和维护。不建议在生产环境中使用 ABAC。

RBAC

使用 RBAC,通过分配给用户的角色来规范对资源的访问。自 v1.8 以来,RBAC 在许多集群中默认启用。要启用 RBAC,请使用--authorization-mode=RBAC启动 API 服务器:

$ ps aux | grep api
root     14632  9.2 17.0 495148 338780 ?       Ssl  06:11   0:09 kube-apiserver --advertise-address=192.168.99.104 --allow-privileged=true --authorization-mode=Node,RBAC ...

RBAC 使用 Role,这是一组权限,以及 RoleBinding,它向用户授予权限。Role 和 RoleBinding 受到命名空间的限制。如果角色需要跨命名空间,则可以使用 ClusterRole 和 ClusterRoleBinding 来向用户授予权限。

以下是允许用户在默认命名空间中创建和修改 Pod 的Role属性示例:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: deployment-manager
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

相应的RoleBinding可以与Role一起使用,向用户授予权限:

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: binding
  namespace: default
subjects:
- kind: User
  name: employee
  apiGroup: ""
roleRef:
  kind: Role
  name: deployment-manager
  apiGroup: ""

一旦应用了RoleBinding,您可以切换上下文查看是否工作正常:

$ kubectl --context=employee-context get pods
NAME                          READY   STATUS    RESTARTS   AGE
hello-node-677b9cfc6b-xks5f   1/1     Running   0          12m

但是,如果尝试查看部署,将导致错误:

$ kubectl --context=employee-context get deployments
Error from server (Forbidden): deployments.apps is forbidden: User "employee" cannot list resource "deployments" in API group "apps" in the namespace "default"

由于角色和角色绑定受限于默认命名空间,访问不同命名空间中的 Pod 将导致错误:

$ kubectl --context=employee-context get pods -n test
Error from server (Forbidden): pods is forbidden: User "test" cannot list resource "pods" in API group "" in the namespace "test"
$ kubectl --context=employee-context get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "test" cannot list resource "pods" in API group "" in the namespace "kube-system"

接下来,我们将讨论 webhooks,它为企业提供了使用远程服务器进行授权的能力。

Webhooks

类似于用于身份验证的 webhook 模式,用于授权的 webhook 模式使用远程 API 服务器来检查用户权限。可以通过使用--authorization-webhook-config-file=<path>来启用 webhook 模式。

让我们看一个示例 webhook 配置文件,将authz.remote设置为 Kubernetes 集群的远程授权端点:

clusters:
  - name: authz_service
    cluster:
      certificate-authority: ca.pem
      server: https://authz.remote/

一旦请求通过了认证和授权模块,准入控制器就会处理请求。让我们详细讨论准入控制器。

准入控制器

准入控制器是在请求经过认证和授权后拦截 API 服务器的模块。控制器在修改集群中对象的状态之前验证和改变请求。控制器可以是改变和验证的。如果任何控制器拒绝请求,请求将立即被丢弃,并向用户返回错误,以便请求不会被处理。

可以通过使用--enable-admission-plugins标志来启用准入控制器:

$ps aux | grep api
root      3460 17.0  8.6 496896 339432 ?       Ssl  06:53   0:09 kube-apiserver --advertise-address=192.168.99.106 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=PodSecurityPolicy,NamespaceLifecycle,LimitRanger --enable-bootstrap-token-auth=true

可以使用--disable-admission-plugins标志来禁用默认的准入控制器。

在接下来的章节中,我们将看一些重要的准入控制器。

AlwaysAdmit

此准入控制器允许所有的 Pod 存在于集群中。自 1.13 版本以来,该控制器已被弃用,不应在任何集群中使用。使用此控制器,集群的行为就好像集群中不存在任何控制器一样。

AlwaysPullImages

该控制器确保新的 Pod 始终强制拉取镜像。这有助于确保 Pod 使用更新的镜像。它还确保只有有权限访问的用户才能使用私有镜像,因为没有访问权限的用户在启动新的 Pod 时无法拉取镜像。应该在您的集群中启用此控制器。

事件速率限制

拒绝服务攻击在基础设施中很常见。行为不端的对象也可能导致资源的高消耗,如 CPU 或网络,从而导致成本增加或可用性降低。EventRateLimit用于防止这些情况发生。

限制是使用配置文件指定的,可以通过向 API 服务器添加 --admission-control-config-file 标志来指定。

集群可以有四种类型的限制:NamespaceServerUserSourceAndObject。对于每个限制,用户可以拥有 每秒查询 (QPS)、突发和缓存大小的最大限制。

让我们看一个配置文件的例子:

limits:
- type: Namespace
  qps: 50
  burst: 100
  cacheSize: 200
- type: Server
  qps: 10
  burst: 50
  cacheSize: 200

这将为所有 API 服务器和命名空间添加 qpsburstcacheSize 限制。

接下来,我们将讨论 LimitRanger,它可以防止集群中可用资源的过度利用。

LimitRanger

这个准入控制器观察传入的请求,并确保它不违反 LimitRange 对象中指定的任何限制。

一个 LimitRange 对象的例子如下:

apiVersion: "v1"
kind: "LimitRange"
metadata:
  name: "pod-example" 
spec:
  limits:
    - type: "Pod"
      max:
        memory: "128Mi"

有了这个限制范围对象,任何请求内存超过 128 Mi 的 pod 都将失败:

pods "range-demo" is forbidden maximum memory usage per Pod is 128Mi, but limit is 1073741824

在使用 LimitRanger 时,恶意的 pod 无法消耗过多的资源。

NodeRestriction

这个准入控制器限制了 kubelet 可以修改的 pod 和节点。有了这个准入控制器,kubelet 以 system:node:<name> 格式获得一个用户名,并且只能修改自己节点上运行的节点对象和 pod。

PersistentVolumeClaimResize

这个准入控制器为 PersistentVolumeClaimResize 请求添加了验证。

PodSecurityPolicy

这个准入控制器在创建或修改 pod 时运行,以确定是否应该基于 pod 的安全敏感配置来运行 pod。策略中的一组条件将与工作负载配置进行检查,以验证是否应该允许工作负载创建请求。PodSecurityPolicy 可以检查诸如 privilegedallowHostPathsdefaultAddCapabilities 等字段。您将在下一章中了解更多关于 PodSecurityPolicy 的内容。

SecurityContextDeny

如果未启用 PodSecurityPolicy,则建议使用此准入控制器。它限制了安全敏感字段的设置,这可能会导致特权升级,例如运行特权 pod 或向容器添加 Linux 功能:

$ ps aux | grep api
root      3763  6.7  8.7 497344 345404 ?       Ssl  23:28   0:14 kube-apiserver --advertise-address=192.168.99.112 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=SecurityContextDeny

建议默认情况下在集群中启用 PodSecurityPolicy。但是,由于管理开销,可以在为集群配置 PodSecurityPolicy 之前使用 SecurityContextDeny

ServiceAccount

ServiceAccount是 pod 的身份。这个准入控制器实现了ServiceAccount;如果集群使用服务账户,应该使用它。

MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook

类似于用于身份验证和授权的 webhook 配置,webhook 可以用作准入控制器。MutatingAdmissionWebhook 修改工作负载的规范。这些钩子按顺序执行。ValidatingAdmissionWebhook 解析传入的请求以验证其是否正确。验证钩子同时执行。

现在,我们已经了解了 Kubernetes 中资源的身份验证、授权和准入控制。让我们看看开发人员如何在他们的集群中实现细粒度的访问控制。在下一节中,我们将讨论 OPA,这是一个在生产集群中广泛使用的开源工具。

OPA 简介

OPA是一个开源的策略引擎,允许在 Kubernetes 中执行策略。许多开源项目,如 Istio,利用 OPA 提供更精细的控制。OPA 是由Cloud Native Computing Foundation (CNCF)托管的孵化项目。

OPA 部署为与其他服务一起的服务。为了做出授权决策,微服务调用 OPA 来决定请求是否应该被允许或拒绝。授权决策被卸载到 OPA,但这种执行需要由服务本身实现。在 Kubernetes 环境中,它经常被用作验证 webhook:

图 7.2 - 开放策略代理

图 7.2 - 开放策略代理

为了做出策略决策,OPA 需要以下内容:

  • 集群信息:集群的状态。集群中可用的对象和资源对于 OPA 来说是重要的,以便决定是否应该允许请求。

  • 输入查询:策略代理分析请求的参数,以允许或拒绝请求。

  • 策略:策略定义了解析集群信息和输入查询以返回决策的逻辑。OPA 的策略是用一种称为 Rego 的自定义语言定义的。

让我们看一个例子,说明如何利用 OPA 来拒绝创建带有busybox镜像的 pod。您可以使用官方的 OPA 文档(www.openpolicyagent.org/docs/latest/kubernetes-tutorial/)在您的集群上安装 OPA。

以下是限制使用busybox镜像创建和更新 pod 的策略:

$ cat pod-blacklist.rego
package kubernetes.admission
import data.kubernetes.namespaces
operations = {"CREATE", "UPDATE"}
deny[msg] {
	input.request.kind.kind == "Pod"
	operations[input.request.operation]
	image := input.request.object.spec.containers[_].image
	image == "busybox"
	msg := sprintf("image not allowed %q", [image])
}

要应用此策略,您可以使用以下内容:

kubectl create configmap pod —from-file=pod-blacklist.rego

一旦创建了configmapkube-mgmt会从configmap中加载这些策略,在opa容器中,kube-mgmtopa容器都在opa pod 中。现在,如果您尝试使用busybox镜像创建一个 pod,您将得到以下结果:

$ cat busybox.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]

该策略检查对busybox镜像名称的请求,并拒绝使用busybox镜像创建带有image not allowed错误的 pod:

admission webhook "validating-webhook.openpolicyagent.org" denied the request: image not allowed "busybox"

类似于我们之前讨论过的准入控制器,可以使用 OPA 在 Kubernetes 集群中创建进一步细粒度的准入控制器。

总结

在本章中,我们讨论了在 Kubernetes 中进行身份验证和授权的重要性。我们讨论了可用于身份验证和授权的不同模块,并详细讨论了这些模块,以及详细介绍了每个模块的使用示例。在讨论身份验证时,我们讨论了用户模拟,这可以由集群管理员或开发人员用来测试权限。接下来,我们谈到了准入控制器,它可以用于在身份验证和授权之后验证或改变请求。我们还详细讨论了一些准入控制器。最后,我们看了一下 OPA,它可以在 Kubernetes 集群中执行更细粒度的授权。

现在,您应该能够为您的集群制定适当的身份验证和授权策略。您应该能够确定哪些准入控制器适用于您的环境。在许多情况下,您将需要更细粒度的授权控制,这可以通过使用 OPA 来实现。

在下一章中,我们将深入探讨保护 pod。本章将更详细地涵盖我们在本章中涵盖的一些主题,如 PodSecurityPolicy。保护 pod 对于保护 Kubernetes 中的应用部署至关重要。

问题

  1. 哪些授权模块不应该在集群中使用?

  2. 集群管理员如何测试对新用户授予的权限?

  3. 哪些授权模式适合生产集群?

  4. EventRateLimitLimitRange准入控制器之间有什么区别?

  5. 您能否编写一个 Rego 策略来拒绝创建带有test.example端点的 ingress?

进一步阅读

您可以参考以下链接获取更多信息:

第八章:保护 Kubernetes Pods

尽管 pod 是作为运行微服务的最细粒度单位,保护 Kubernetes pods 是一个广泛的主题,因为它应该涵盖整个 DevOps 流程:构建、部署和运行。

在本章中,我们选择将焦点缩小到构建和运行阶段。为了在构建阶段保护 Kubernetes pods,我们将讨论如何加固容器镜像并配置 pod(或 pod 模板)的安全属性,以减少攻击面。虽然一些工作负载的安全属性,如 AppArmor 和 SELinux 标签,会在运行阶段生效,但安全控制已经为工作负载定义好了。为了进一步澄清问题,我们试图通过在构建阶段配置运行效果的安全属性来保护 Kubernetes 工作负载。为了在运行阶段保护 Kubernetes pods,我们将介绍一个带有示例的 PodSecurityPolicy 以及辅助工具kube-psp-advisor

后续章节将更详细地讨论运行时安全和响应。还要注意,应用程序的利用可能导致 pod 被 compromise。但是,我们不打算在本章中涵盖应用程序。

在本章中,我们将涵盖以下主题:

  • 容器镜像的加固

  • 配置 pod 的安全属性

  • PodSecurityPolicy 的威力

容器镜像的加固

容器镜像的加固意味着遵循安全最佳实践或基线,以配置容器镜像,以减少攻击面。镜像扫描工具只关注在镜像内捆绑的应用程序中找到的公开披露的问题。但是,在构建镜像时遵循最佳实践以及安全配置,可以确保应用程序具有最小的攻击面。

在我们开始讨论安全配置基线之前,让我们看看容器镜像是什么,以及 Dockerfile 是什么,以及它是如何用来构建镜像的。

容器镜像和 Dockerfile

一个容器镜像是一个文件,它捆绑了微服务二进制文件、它的依赖项和微服务的配置等。一个容器是镜像的运行实例。如今,应用程序开发人员不仅编写代码来构建微服务;他们还需要构建 Dockerfile 来将微服务容器化。为了帮助构建容器镜像,Docker 提供了一种标准化的方法,称为 Dockerfile。一个Dockerfile包含一系列的指令,比如复制文件、配置环境变量、配置开放端口和容器入口点,这些指令可以被 Docker 守护进程理解以构建镜像文件。然后,镜像文件将被推送到镜像注册表,然后从那里部署到 Kubernetes 集群中。每个 Dockerfile 指令都会在镜像中创建一个文件层。

在我们看一个 Dockerfile 的例子之前,让我们先了解一些基本的 Dockerfile 指令:

  • FROM:从基础镜像或父镜像初始化一个新的构建阶段。两者都意味着你正在捆绑自己的镜像的基础或文件层。

  • RUN:执行命令并将结果提交到上一个文件层之上。

  • ENV:为运行的容器设置环境变量。

  • CMD:指定容器将运行的默认命令。

  • COPY/ADD:这两个命令都是将文件或目录从本地(或远程)URL 复制到镜像的文件系统中。

  • EXPOSE:指定微服务在容器运行时将监听的端口。

  • ENTRYPOINT:类似于CMD,唯一的区别是ENTRYPOINT会使容器作为可执行文件运行。

  • WORKDIR:为接下来的指令设置工作目录。

  • USER:为容器的CMD/ENTRYPOINT设置用户和组 ID。

现在,让我们来看一个 Dockerfile 的例子:

FROM ubuntu
# install dependencies
RUN apt-get install -y software-properties-common python
RUN add-apt-repository ppa:chris-lea/node.js
RUN echo "deb http://us.archive.ubuntu.com/ubuntu/ precise universe" >> /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y nodejs
# make directory
RUN mkdir /var/www
# copy app.js
ADD app.js /var/www/app.js
# set the default command to run
CMD ["/usr/bin/node", "/var/www/app.js"]

从上面的 Dockerfile 中,我们可以看出这个镜像是基于ubuntu构建的。然后,它运行了一系列的apt-get命令来安装依赖,并创建了一个名为/var/www的目录。接下来,将app.js文件从当前目录复制到镜像文件系统中的/var/www/app.js。最后,配置默认命令来运行这个Node.js应用程序。我相信当你开始构建镜像时,你会看到 Dockerfile 是多么简单和强大。

下一个问题是安全问题,因为看起来您可以构建任何类型的图像。接下来,让我们谈谈 CIS Docker 基准。

CIS Docker 基准

互联网安全中心(CIS)制定了有关 Docker 容器管理和管理的指南。现在,让我们来看看 CIS Docker 基准关于容器图像的安全建议:

  • 为容器图像创建一个用户来运行微服务:以非 root 用户运行容器是一个好的做法。虽然用户命名空间映射是可用的,但默认情况下未启用。以 root 身份运行意味着如果攻击者成功逃离容器,他们将获得对主机的 root 访问权限。在 Dockerfile 中使用USER指令创建一个用户。

  • 使用受信任的基础图像构建您自己的图像:从公共存储库下载的图像不能完全信任。众所周知,来自公共存储库的图像可能包含恶意软件或加密货币挖矿程序。因此,建议您从头开始构建图像或使用最小的受信任图像,如 Alpine。此外,在构建图像后执行图像扫描。图像扫描将在下一章节中介绍。

  • 不要在图像中安装不必要的软件包:安装不必要的软件包会增加攻击面。建议保持图像的精简。在构建图像的过程中,您可能需要安装一些工具。请记住在 Dockerfile 的末尾将它们删除。

  • 扫描并重建图像以应用安全补丁:很可能会在基础图像或图像中安装的软件包中发现新的漏洞。经常扫描图像是一个好的做法。一旦发现任何漏洞,尝试通过重建图像来修补安全漏洞。图像扫描是在构建阶段识别漏洞的关键机制。我们将在下一章节详细介绍图像扫描。

  • 为 Docker 启用内容信任:内容信任使用数字签名确保客户端和 Docker 注册表之间的数据完整性。它确保容器图像的来源。但默认情况下未启用。您可以通过将环境变量DOCKER_CONTENT_TRUST设置为1来启用它。

  • 向容器图像添加 HEALTHCHECK 指令HEALTHCHECK指令定义了一个命令,要求 Docker 引擎定期检查容器的健康状态。根据健康状态检查结果,Docker 引擎然后退出不健康的容器并启动一个新的容器。

  • 确保 Dockerfile 中的更新不被缓存:根据您选择的基础镜像,您可能需要在安装新软件包之前更新软件包存储库。但是,如果您在 Dockerfile 中的单行中指定RUN apt-get update``(Debian),Docker 引擎将缓存此文件层,因此,当您再次构建图像时,它仍将使用缓存的旧软件包存储库信息。这将阻止您在图像中使用最新的软件包。因此,要么在单个 Dockerfile 指令中同时使用updateinstall,要么在 Dockerbuild命令中使用--no-cache标志。

  • 从图像中删除 setuid 和 setgid 权限setuidsetgid权限可用于特权升级,因为具有这些权限的文件允许以所有者特权而不是启动器特权执行。您应该仔细审查具有setuidsetgid权限的文件,并删除不需要此类权限的文件。

  • 在 Dockerfile 中使用 COPY 而不是 ADDCOPY指令只能将文件从本地计算机复制到图像的文件系统,而ADD指令不仅可以从本地计算机复制文件,还可以从远程 URL 检索文件到图像的文件系统。使用ADD可能会引入从互联网添加恶意文件的风险。

  • 不要在 Dockerfile 中存储机密信息:有许多工具可以提取图像文件层。如果图像中存储了任何机密信息,那么这些机密信息就不再是机密信息。在 Dockerfile 中存储机密信息会使容器有潜在的可利用性。一个常见的错误是使用ENV指令将机密信息存储在环境变量中。

  • 仅安装经过验证的软件包:这类似于仅使用受信任的基础镜像。在安装图像内的软件包时要小心,确保它们来自受信任的软件包存储库。

如果您遵循前述 CIS Docker 基准的安全建议,您将成功地加固容器镜像。这是在构建阶段保护 pod 的第一步。现在,让我们看看我们需要注意的安全属性,以确保 pod 的安全。

配置 pod 的安全属性

正如我们在前一章中提到的,应用程序开发人员应该知道微服务必须具有哪些特权才能执行任务。理想情况下,应用程序开发人员和安全工程师应该共同努力,通过配置 Kubernetes 提供的安全上下文来加固 pod 和容器级别的微服务。

我们将主要的安全属性分为四类:

  • 为 pod 设置主机命名空间

  • 容器级别的安全上下文

  • Pod 级别的安全上下文

  • AppArmor 配置文件

通过采用这种分类方式,您会发现它们易于管理。

为 pod 设置主机级别的命名空间

在 pod 规范中使用以下属性来配置主机命名空间的使用:

  • hostPID:默认情况下为false。将其设置为true允许 pod 在工作节点上看到所有进程。

  • hostNetwork:默认情况下为false。将其设置为true允许 pod 在工作节点上看到所有网络堆栈。

  • hostIPC:默认情况下为false。将其设置为true允许 pod 在工作节点上看到所有 IPC 资源。

以下是如何在ubuntu-1 pod 的YAML文件中配置在 pod 级别使用主机命名空间的示例:

apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-1
  labels:
    app: util
spec:
  containers:
  - name: ubuntu
    image: ubuntu
    imagePullPolicy: Always
  hostPID: true
  hostNetwork: true
  hostIPC: true

前面的工作负载 YAML 配置了ubuntu-1 pod 以使用主机级 PID 命名空间、网络命名空间和 IPC 命名空间。请记住,除非必要,否则不应将这些属性设置为true。将这些属性设置为true还会解除同一工作节点上其他工作负载的安全边界,正如在第五章中已经提到的,配置 Kubernetes 安全边界

容器的安全上下文

多个容器可以被分组放置在同一个 Pod 中。每个容器可以拥有自己的安全上下文,定义特权和访问控制。在容器级别设计安全上下文为 Kubernetes 工作负载提供了更精细的安全控制。例如,您可能有三个容器在同一个 Pod 中运行,其中一个必须以特权模式运行,而其他的以非特权模式运行。这可以通过为各个容器配置安全上下文来实现。

以下是容器安全上下文的主要属性:

  • privileged: 默认情况下为false。将其设置为true实质上使容器内的进程等同于工作节点上的 root 用户。

  • 能力: 容器运行时默认授予容器的一组能力。默认授予的能力包括:CAP_SETPCAPCAP_MKNODCAP_AUDIT_WRITECAP_CHOWNCAP_NET_RAWCAP_DAC_OVERRIDECAP_FOWNERCAP_FSETIDCAP_KILLCAP_SETGIDCAP_SETUIDCAP_NET_BIND_SERVICECAP_SYS_CHROOTCAP_SETFCAP

您可以通过配置此属性添加额外的能力或删除一些默认的能力。诸如CAP_SYS_ADMINCAP_NETWORK_ADMIN之类的能力应谨慎添加。对于默认的能力,您还应该删除那些不必要的能力。

  • allowPrivilegeEscalation: 默认情况下为true。直接设置此属性可以控制no_new_privs标志,该标志将设置为容器中的进程。基本上,此属性控制进程是否可以获得比其父进程更多的特权。请注意,如果容器以特权模式运行,或者添加了CAP_SYS_ADMN能力,此属性将自动设置为true。最好将其设置为false

  • readOnlyRootFilesystem: 默认情况下为false。将其设置为true会使容器的根文件系统变为只读,这意味着库文件、配置文件等都是只读的,不能被篡改。将其设置为true是一个良好的安全实践。

  • runAsNonRoot:默认情况下为false。将其设置为true可以启用验证,以确保容器中的进程不能以 root 用户(UID=0)身份运行。验证由kubelet执行。将runAsNonRoot设置为true后,如果以 root 用户身份运行,kubelet将阻止容器启动。将其设置为true是一个良好的安全实践。这个属性也可以在PodSecurityContext中使用,在 Pod 级别生效。如果在SecurityContextPodSecurityContext中都设置了这个属性,那么在容器级别指定的值优先。

  • runAsUser:这是用来指定容器镜像入口进程运行的 UID。默认设置是镜像元数据中指定的用户(例如,Dockerfile 中的USER指令)。这个属性也可以在PodSecurityContext中使用,在 Pod 级别生效。如果在SecurityContextPodSecurityContext中都设置了这个属性,那么在容器级别指定的值优先。

  • runAsGroup:类似于runAsUser,这是用来指定容器入口进程运行的Group IDGID。这个属性也可以在PodSecurityContext中使用,在 Pod 级别生效。如果在SecurityContextPodSecurityContext中都设置了这个属性,那么在容器级别指定的值优先。

  • seLinuxOptions:这是用来指定容器的 SELinux 上下文的。默认情况下,如果未指定,容器运行时将为容器分配一个随机的 SELinux 上下文。这个属性也可以在PodSecurityContex中使用,在 Pod 级别生效。如果在SecurityContextPodSecurityContext中都设置了这个属性,那么在容器级别指定的值优先。

既然你现在了解了这些安全属性是什么,你可以根据自己的业务需求提出自己的加固策略。一般来说,安全最佳实践如下:

  • 除非必要,不要以特权模式运行。

  • 除非必要,不要添加额外的能力。

  • 丢弃未使用的默认能力。

  • 以非 root 用户身份运行容器。

  • 启用runAsNonRoot检查。

  • 将容器根文件系统设置为只读。

现在,让我们看一个为容器配置SecurityContext的示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: web
spec:
  hostNetwork: false
  hostIPC: false
  hostPID: false
  containers:
  - name: nginx
    image: kaizheh/nginx 
    securityContext:
      privileged: false
      capabilities:
        add:
        - NETWORK_ADMIN
      readOnlyRootFilesystem: true 
      runAsUser: 100
      runAsGroup: 1000

nginx-pod 内的nginx容器以 UID 为100和 GID 为1000的用户身份运行。除此之外,nginx容器还获得了额外的NETWORK_ADMIN权限,并且根文件系统被设置为只读。这里的 YAML 文件只是展示了如何配置安全上下文的示例。请注意,在生产环境中运行的容器中不建议添加NETWORK_ADMIN

Pod 的安全上下文

安全上下文是在 pod 级别使用的,这意味着安全属性将应用于 pod 内的所有容器。

以下是 pod 级别的主要安全属性列表:

  • fsGroup:这是一个应用于所有容器的特殊辅助组。这个属性的有效性取决于卷类型。基本上,它允许kubelet将挂载卷的所有权设置为具有辅助 GID 的 pod。

  • sysctlssysctls用于在运行时配置内核参数。在这样的上下文中,sysctls和内核参数是可以互换使用的。这些sysctls命令是命名空间内的内核参数,适用于 pod。以下sysctls命令已知是命名空间内的:kernel.shm*kernel.msg*kernel.semkernel.mqueue.*。不安全的sysctls默认情况下是禁用的,不应在生产环境中启用。

  • runAsUser:这是用来指定容器镜像的入口进程运行的 UID 的。默认设置是镜像元数据中指定的用户(例如,Dockerfile 中的USER指令)。这个属性也可以在SecurityContext中使用,它在容器级别生效。如果在SecurityContextPodSecurityContext中都设置了这个属性,那么在容器级别指定的值会优先生效。

  • runAsGroup:类似于runAsUser,这是用来指定容器的入口进程运行的 GID 的。这个属性也可以在SecurityContext中使用,它在容器级别生效。如果在SecurityContextPodSecurityContext中都设置了这个属性,那么在容器级别指定的值会优先生效。

  • runAsNonRoot:默认情况下设置为false,将其设置为true可以启用验证,即容器中的进程不能以 root 用户(UID=0)身份运行。验证由kubelet执行。将其设置为truekubelet将阻止以 root 用户身份运行的容器启动。将其设置为true是一个很好的安全实践。此属性也可在SecurityContext中使用,其在容器级别生效。如果在SecurityContextPodSecurityContext中都设置了此属性,则以容器级别指定的值优先。

  • seLinuxOptions:这是用来指定容器的 SELinux 上下文的。如果未指定,默认情况下,容器运行时会为容器分配一个随机的 SELinux 上下文。此属性也可在SecurityContext中使用,其在容器级别生效。如果在SecurityContextPodSecurityContext中都设置了此属性,则以容器级别指定的值优先。

请注意,runAsUserrunAsGrouprunAsNonRootseLinuxOptions属性在容器级别的SecurityContext和 pod 级别的PodSecurityContext中都可用。这为用户提供了灵活性和极其重要的安全控制。fsGroupsysctls不像其他属性那样常用,所以只有在必要时才使用它们。

AppArmor 配置文件

AppArmor 配置文件通常定义了进程拥有的 Linux 功能,容器可以访问的网络资源和文件等。为了使用 AppArmor 配置文件保护 pod 或容器,您需要更新 pod 的注释。让我们看一个例子,假设您有一个 AppArmor 配置文件来阻止任何文件写入活动。

#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>
  file,
  # Deny all file writes.
  deny /** w,
}

请注意,AppArmor 不是 Kubernetes 对象,如 pod、部署等。它不能通过kubectl操作。您需要 SSH 到每个节点,并将 AppArmor 配置文件加载到内核中,以便 pod 可以使用它。

以下是加载 AppArmor 配置文件的命令:

cat /etc/apparmor.d/profile.name | sudo apparmor_parser -a

然后,将配置文件放入enforce模式:

sudo aa-enforce /etc/apparmor.d/profile.name

一旦加载了 AppArmor 配置文件,您可以更新 pod 的注释,以使用 AppArmor 配置文件保护您的容器。以下是将 AppArmor 配置文件应用于容器的示例:

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
  annotations:
    # Tell Kubernetes to apply the AppArmor profile 
    # "k8s-apparmor-example-deny-write".
    container.apparmor.security.beta.kubernetes.io/hello: 
      localhost/k8s-apparmor-example-deny-write
spec:
  containers:
  - name: hello
    image: busybox
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]

hello-apparmor内的容器除了在回显“Hello AppArmor!”消息后进入睡眠状态外,什么也不做。当它运行时,如果您从容器中启动一个 shell 并写入任何文件,AppArmor 将会阻止。尽管编写健壮的 AppArmor 配置文件并不容易,但您仍然可以创建一些基本的限制,比如拒绝写入到某些目录,拒绝接受原始数据包,并使某些文件只读。此外,在将配置应用到生产集群之前,先测试配置文件。开源工具如 bane 可以帮助为容器创建 AppArmor 配置文件。

我们不打算在本书中深入讨论 seccomp 配置文件,因为为微服务编写 seccomp 配置文件并不容易。即使是应用程序开发人员也不知道他们开发的微服务有哪些系统调用是合法的。尽管您可以打开审计模式以避免破坏微服务的功能,但构建健壮的 seccomp 配置文件仍然任重道远。另一个原因是,这个功能在版本 1.17 之前仍处于 alpha 阶段。根据 Kubernetes 的官方文档,alpha 表示默认情况下禁用,可能存在错误,并且只建议在短期测试集群中运行。当 seccomp 有任何新的更新时,我们可能会在以后更详细地介绍 seccomp。

我们已经介绍了如何在构建时保护 Kubernetes Pod。接下来,让我们看看如何在运行时保护 Kubernetes Pod。

PodSecurityPolicy 的力量

Kubernetes PodSecurityPolicy 是一个集群级资源,通过它可以控制 pod 规范的安全敏感方面,从而限制 Kubernetes pod 的访问权限。作为一名 DevOps 工程师,您可能希望使用 PodSecurityPolicy 来限制大部分工作负载以受限访问权限运行,同时只允许少数工作负载以额外权限运行。

在本节中,我们将首先仔细研究 PodSecurityPolicy,然后介绍一个名为 kube-psp-advisor 的开源工具,它可以帮助为运行中的 Kubernetes 集群构建一个自适应的 PodSecurityPolicy。

理解 PodSecurityPolicy

您可以将 PodSecurityPolicy 视为评估 Pod 规范中定义的安全属性的策略。只有那些安全属性符合 PodSecurityPolicy 要求的 Pod 才会被允许进入集群。例如,PodSecurityPolicy 可以用于阻止启动大多数特权 Pod,同时只允许那些必要或受限制的 Pod 访问主机文件系统。

以下是由 PodSecurityPolicy 控制的主要安全属性:

  • privileged: 确定 Pod 是否可以以特权模式运行。

  • hostPID: 确定 Pod 是否可以使用主机 PID 命名空间。

  • hostNetwork: 确定 Pod 是否可以使用主机网络命名空间。

  • hostIPC: 确定 Pod 是否可以使用主机 IPC 命名空间。默认设置为true

  • allowedCapabilities: 指定可以添加到容器中的功能列表。默认设置为空。

  • defaultAddCapabilities: 指定默认情况下将添加到容器中的功能列表。默认设置为空。

  • requiredDropCapabilities: 指定将从容器中删除的功能列表。请注意,功能不能同时在allowedCapabilitiesrequiredDropCapabilities字段中指定。默认设置为空。

  • readOnlyRootFilesystem: 当设置为true时,PodSecurityPolicy 将强制容器以只读根文件系统运行。如果容器的安全上下文中明确将属性设置为false,则将拒绝 Pod 运行。默认设置为false

  • runAsUser: 指定可以在 Pod 和容器的安全上下文中设置的允许用户 ID 列表。默认设置允许所有。

  • runAsGroup: 指定可以在 Pod 和容器的安全上下文中设置的允许组 ID 列表。默认设置允许所有。

  • allowPrivilegeEscalation: 确定 Pod 是否可以提交请求以允许特权升级。默认设置为true

  • allowedHostPaths: 指定 Pod 可以挂载的主机路径列表。默认设置允许所有。

  • : 指定可以由 Pod 挂载的卷类型列表。例如,secretconfigmaphostpath是有效的卷类型。默认设置允许所有。

  • seLinux: 指定可以在 Pod 和容器的安全上下文中设置的允许seLinux标签列表。默认设置允许所有。

  • allowedUnsafeSysctl:允许运行不安全的sysctls。默认设置不允许任何。

现在,让我们来看一个 PodSecurityPolicy 的例子:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
    name: example
spec:
  allowedCapabilities:
  - NET_ADMIN
  - IPC_LOCK
  allowedHostPaths:
  - pathPrefix: /dev
  - pathPrefix: /run
  - pathPrefix: /
  fsGroup:
    rule: RunAsAny
  hostNetwork: true
  privileged: true
  runAsUser:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - hostPath
  - secret

这个 PodSecurityPolicy 允许NET_ADMINIPC_LOCK的权限,从主机和 Kubernetes 的秘密卷挂载//dev/run。它不强制执行任何文件系统组 ID 或辅助组,也允许容器以任何用户身份运行,访问主机网络命名空间,并以特权容器运行。策略中没有强制执行 SELinux 策略。

要启用此 Pod 安全策略,您可以运行以下命令:

$ kubectl apply -f example-psp.yaml

现在,让我们验证 Pod 安全策略是否已成功创建:

$ kubectl get psp

输出将如下所示:

NAME      PRIV     CAPS                           SELINUX    RUNASUSER   FSGROUP    SUPGROUP   READONLYROOTFS   VOLUMES
example   true     NET_ADMIN, IPC_LOCK            RunAsAny   RunAsAny    RunAsAny   RunAsAny   false            hostPath,secret

创建了 Pod 安全策略后,还需要另一步来强制执行它。您将需要授予用户、组或服务帐户使用PodSecurityPolicy对象的特权。通过这样做,Pod 安全策略有权根据关联的服务帐户评估工作负载。以下是如何强制执行 PodSecurityPolicy 的示例。首先,您需要创建一个使用 PodSecurityPolicy 的集群角色:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: use-example-psp
rules:
- apiGroups: ['policy']
  resources: ['podsecuritypolicies']
  verbs:     ['use']
  resourceNames:
  - example

然后,创建一个RoleBindingClusterRoleBinding对象,将之前创建的ClusterRole对象与服务帐户、用户或组关联起来:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: use-example-psp-binding
roleRef:
  kind: ClusterRole
  name: use-example-psp
  apiGroup: rbac.authorization.k8s.io
subjects:
# Authorize specific service accounts:
- kind: ServiceAccount
  name: test-sa
  namespace: psp-test

之前创建的use-example-pspbinding.yaml文件创建了一个RoleBinding对象,将use-example-psp集群角色与psp-test命名空间中的test-sa服务帐户关联起来。通过所有这些设置,psp-test命名空间中其服务帐户为test-sa的任何工作负载将通过 PodSecurityPolicy 示例的评估。只有符合要求的工作负载才能被允许进入集群。

从上面的例子中,想象一下在您的 Kubernetes 集群中运行不同类型的工作负载,每个工作负载可能需要不同的特权来访问不同类型的资源。为不同的工作负载创建和管理 Pod 安全策略将是一个挑战。现在,让我们来看看kube-psp-advisor,看看它如何帮助您创建 Pod 安全策略。

Kubernetes PodSecurityPolicy Advisor

Kubernetes PodSecurityPolicy Advisor(也称为kube-psp-advisor)是来自 Sysdig 的开源工具。它扫描集群中运行的工作负载的安全属性,然后基于此推荐您的集群或工作负载的 Pod 安全策略。

首先,让我们将kube-psp-advisor作为kubectl插件进行安装。如果您还没有安装krew,请按照说明(https://github.com/kubernetes-sigs/krew#installation)安装它。然后,使用krew安装kube-psp-advisor如下:

$ kubectl krew install advise-psp

然后,您应该能够运行以下命令来验证安装:

$ kubectl advise-psp
A way to generate K8s PodSecurityPolicy objects from a live K8s environment or individual K8s objects containing pod specifications
Usage:
  kube-psp-advisor [command]
Available Commands:
  convert     Generate a PodSecurityPolicy from a single K8s Yaml file
  help        Help about any command
  inspect     Inspect a live K8s Environment to generate a PodSecurityPolicy
Flags:
  -h, --help           help for kube-psp-advisor
      --level string   Log level (default "info")

要为命名空间中的工作负载生成 Pod 安全策略,可以运行以下命令:

$ kubectl advise-psp inspect --grant --namespace psp-test

上述命令为在psp-test命名空间内运行的工作负载生成了 Pod 安全策略。如果工作负载使用默认服务账户,则不会为其生成 PodSecurityPolicy。这是因为默认服务账户将被分配给没有专用服务账户关联的工作负载。当然,您肯定不希望默认服务账户能够使用特权工作负载的 PodSecurityPolicy。

以下是kube-psp-advisorpsp-test命名空间中的工作负载生成的输出示例,包括 Role、RoleBinding 和 PodSecurityPolicy 在一个单独的 YAML 文件中,其中包含多个 Pod 安全策略。让我们来看一个推荐的 PodSecurityPolicy:

# Pod security policies will be created for service account 'sa-1' in namespace 'psp-test' with following workloads:
#	Kind: ReplicaSet, Name: busy-rs, Image: busybox
#	Kind: Pod, Name: busy-pod, Image: busybox
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  creationTimestamp: null
  name: psp-for-psp-test-sa-1
spec:
  allowedCapabilities:
  - SYS_ADMIN
  allowedHostPaths:
  - pathPrefix: /usr/bin
    readOnly: true
  fsGroup:
    rule: RunAsAny
  hostIPC: true
  hostNetwork: true
  hostPID: true
  runAsUser:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - configMap
  - secret
  - hostPath

以下是由kube-psp-advisor生成的 Role:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  creationTimestamp: null
  name: use-psp-by-psp-test:sa-1
  namespace: psp-test
rules:
- apiGroups:
  - policy
  resourceNames:
  - psp-for-psp-test-sa-1
  resources:
  - podsecuritypolicies
  verbs:
  - use
---

以下是由kube-psp-advisor生成的 RoleBinding:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  creationTimestamp: null
  name: use-psp-by-psp-test:sa-1-binding
  namespace: psp-test
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: use-psp-by-psp-test:sa-1
subjects:
- kind: ServiceAccount
  name: sa-1
  namespace: psp-test
---

前面的部分是推荐的 PodSecurityPolicy,psp-for-psp-test-sa-1,适用于busy-rsbusy-pod工作负载,因为这两个工作负载共享相同的服务账户sa-1。因此,分别创建了RoleRoleBinding来使用 Pod 安全策略psp-for-psp-test-sa-1。PodSecurityPolicy 是基于使用sa-1服务账户的工作负载的安全属性的聚合生成的:

---
# Pod security policies will NOT be created for service account 'default' in namespace 'psp-test' with following workdloads:
#	Kind: ReplicationController, Name: busy-rc, Image: busybox
---

前面的部分提到busy-rc工作负载使用default服务账户,因此不会为其创建 Pod 安全策略。这是一个提醒,如果要为工作负载生成 Pod 安全策略,请不要使用默认服务账户。

构建 Kubernetes PodSecurityPolicy 并不是一件简单的事情,尽管如果一个受限的 PodSecurityPolicy 适用于整个集群,并且所有工作负载都符合它将是理想的。DevOps 工程师需要有创造力,以便构建受限的 Pod 安全策略,同时不破坏工作负载的功能。kube-psp-advisor使得实施 Kubernetes Pod 安全策略变得简单,适应您的应用程序要求,并且特别为每个应用程序提供了细粒度的权限,只允许最少访问权限的特权。

总结

在本章中,我们介绍了如何使用 CIS Docker 基准来加固容器镜像,然后详细介绍了 Kubernetes 工作负载的安全属性。接下来,我们详细介绍了 PodSecurityPolicy,并介绍了kube-psp-advisor开源工具,该工具有助于建立 Pod 安全策略。

保护 Kubernetes 工作负载不是一蹴而就的事情。安全控制需要从构建、部署和运行阶段应用。它始于加固容器镜像,然后以安全的方式配置 Kubernetes 工作负载的安全属性。这发生在构建阶段。为不同的 Kubernetes 工作负载构建自适应的 Pod 安全策略也很重要。目标是限制大多数工作负载以受限权限运行,同时只允许少数工作负载以额外权限运行,而不会破坏工作负载的可用性。这发生在运行时阶段。kube-psp-advisor能够帮助构建自适应的 Pod 安全策略。

在下一章中,我们将讨论图像扫描。在 DevOps 工作流程中,这对于帮助保护 Kubernetes 工作负载至关重要。

问题

  1. 在 Dockerfile 中,HEALTHCHECK是做什么的?

  2. 为什么在 Dockerfile 中使用COPY而不是ADD

  3. 如果您的应用程序不监听任何端口,可以丢弃哪些默认功能?

  4. runAsNonRoot属性控制什么?

  5. 当您创建一个PodSecurityPolicy对象时,为了强制执行该 Pod 安全策略,您还需要做什么?

进一步阅读

您可以参考以下链接,了解本章涵盖的主题的更多信息:

第九章:DevOps 流水线中的图像扫描

在开发生命周期的早期阶段发现缺陷和漏洞是一个好的做法。在早期阶段识别问题并加以修复有助于提高应用程序的稳健性和稳定性。它还有助于减少生产环境中的攻击面。保护 Kubernetes 集群必须覆盖整个 DevOps 流程。与加固容器图像和在工作负载清单中限制强大的安全属性类似,图像扫描可以帮助改善开发方面的安全姿态。但是,图像扫描绝对可以做得更多。

在本章中,首先,我们将介绍图像扫描和漏洞的概念,然后我们将讨论一个名为 Anchore Engine 的流行开源图像扫描工具,并向您展示如何使用它进行图像扫描。最后但同样重要的是,我们将向您展示如何将图像扫描集成到 CI/CD 流水线中。

在本章之后,您应该熟悉图像扫描的概念,并且可以放心地使用 Anchore Engine 进行图像扫描。更重要的是,如果您还没有这样做,您需要开始考虑将图像扫描集成到您的 CI/CD 流水线中的策略。

在本章中,我们将涵盖以下主题:

  • 介绍容器图像和漏洞

  • 使用 Anchore Engine 扫描图像

  • 将图像扫描集成到 CI/CD 流水线中

介绍容器图像和漏洞

图像扫描可用于识别图像内部的漏洞或违反最佳实践(取决于图像扫描器的能力)。漏洞可能来自图像内的应用程序库或工具。在我们开始图像扫描之前,最好先了解一些关于容器图像和漏洞的知识。

容器图像

容器镜像是一个文件,其中包含了微服务二进制文件、其依赖项、微服务的配置等。如今,应用程序开发人员不仅编写代码来构建微服务,还需要构建一个镜像来容器化应用程序。有时,应用程序开发人员可能不遵循安全最佳实践来编写代码,或者从未经认证的来源下载库。这意味着您自己的应用程序或应用程序依赖的包可能存在漏洞。但不要忘记您使用的基础镜像,其中可能包含另一组脆弱的二进制文件和软件包。因此,首先让我们看一下镜像的样子:

$ docker history kaizheh/anchore-cli
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
76b8613d39bc        8 hours ago         /bin/sh -c #(nop) COPY file:92b27c0a57eddb63…   678B                
38ea9049199d        10 hours ago        /bin/sh -c #(nop)  ENV PATH=/.local/bin/:/us…   0B                  
525287c1340a        10 hours ago        /bin/sh -c pip install anchorecli               5.74MB              
f0cbce9c40f4        10 hours ago        /bin/sh -c apt-get update && apt-get install…   423MB               
a2a15febcdf3        7 months ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           7 months ago        /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>           7 months ago        /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B                
<missing>           7 months ago        /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB               
<missing>           7 months ago        /bin/sh -c #(nop) ADD file:c477cb0e95c56b51e…   63.2MB       

上面的输出显示了镜像kaizheh/anchore-cli的文件层(使用--no-trunc标志显示完整命令)。您可能注意到每个文件层都有一个创建它的相应命令。每个命令之后都会创建一个新的文件层,这意味着镜像的内容已经逐层更新(基本上,Docker 是按写时复制工作的),您仍然可以看到每个文件层的大小。这很容易理解:当您安装新的软件包或向基础添加文件时,镜像的大小会增加。missing镜像 ID 是一个已知的问题,因为 Docker Hub 只存储叶层的摘要,而不是父镜像中的中间层。然而,上述镜像历史确实说明了镜像在 Dockerfile 中的情况,如下所示:

FROM ubuntu
RUN apt-get update && apt-get install -y python-pip jq vim
RUN pip install anchorecli
ENV PATH="$HOME/.local/bin/:$PATH"
COPY ./demo.sh /demo.sh

上述 Dockerfile 的工作原理描述如下:

  1. 构建kaizheh/anchore-cli镜像时,我选择从ubuntu构建。

  2. 然后,我安装了python-pipjqvim软件包。

  3. 接下来,我使用pip安装了anchore-cli,这是我在上一步中安装的。

  4. 然后我配置了环境变量路径。

  5. 最后,我将一个名为demo.sh的 shell 脚本复制到了镜像中。

下图显示了镜像文件层映射到 Dockerfile 指令:

图 9.1 - Dockerfile 指令映射到镜像文件层

图 9.1 - Dockerfile 指令映射到镜像文件层

你不必记住每一层添加了什么。最终,容器镜像是一个压缩文件,其中包含了应用程序所需的所有二进制文件和软件包。当从镜像创建容器时,容器运行时会提取镜像,然后为镜像的提取内容创建一个目录,然后在启动之前为镜像中的入口应用程序配置 chroot、cgroup、Linux 命名空间、Linux 权限等。

现在你知道了容器运行时启动容器的魔法。但你仍然不确定你的镜像是否存在漏洞,以至于可以轻易被黑客攻击。让我们看看镜像扫描到底是做什么。

检测已知漏洞

人们会犯错误,开发人员也一样。如果应用程序中的缺陷是可以利用的,这些缺陷就会成为安全漏洞。漏洞有两种类型,一种是已经被发现的,而另一种则是未知的。安全研究人员、渗透测试人员等都在努力寻找安全漏洞,以减少潜在的妥协。一旦安全漏洞被修补,开发人员会将补丁作为应用程序的更新。如果这些更新没有及时应用,应用程序就有被攻击的风险。如果这些已知的安全问题被恶意人员利用,将给公司造成巨大损失。

在这一部分,我们不会讨论如何寻找安全漏洞。让安全研究人员和道德黑客去做他们的工作。相反,我们将讨论如何通过执行漏洞管理来发现和管理那些由镜像扫描工具发现的已知漏洞。此外,我们还需要了解漏洞是如何在社区中跟踪和共享的。因此,让我们谈谈 CVE 和 NVD。

漏洞数据库简介

CVE 代表通用漏洞和暴露。当发现漏洞时,会为其分配一个唯一的 ID,并附有描述和公共参考。通常,描述中会包含受影响的版本信息。这就是一个 CVE 条目。每天都会发现数百个漏洞,并由 MITRE 分配唯一的 CVE ID。

NVD 代表国家漏洞数据库。它同步 CVE 列表。一旦 CVE 列表有新的更新,新的 CVE 将立即显示在 NVD 中。除了 NVD,还有一些其他的漏洞数据库可用,比如 Synk。

简单来说,图像扫描工具的魔法是:图像扫描工具提取图像文件,然后查找图像中所有可用的软件包和库,并在漏洞数据库中查找它们的版本。如果有任何软件包的版本与漏洞数据库中的任何 CVE 描述匹配,图像扫描工具将报告图像中存在漏洞。如果在容器图像中发现漏洞,您不应感到惊讶。那么,您打算怎么处理呢?您需要做的第一件事是保持冷静,不要惊慌。

漏洞管理

当您有漏洞管理策略时,就不会惊慌。一般来说,每个漏洞管理策略都将从理解漏洞的可利用性和影响开始,这是基于 CVE 详细信息的。NVD 提供了一个漏洞评分系统,也被称为通用漏洞评分系统(CVSS),以帮助您更好地了解漏洞的严重程度。

根据您对漏洞的理解,需要提供以下信息来计算漏洞分数:

  • 攻击向量:利用程序是网络攻击、本地攻击还是物理攻击

  • 攻击复杂性:利用漏洞的难度有多大

  • 所需权限:利用程序是否需要任何权限,如 root 或非 root

  • 用户交互:利用程序是否需要任何用户交互

  • 范围:利用程序是否会导致跨安全域

  • 机密性影响:利用程序对软件机密性的影响程度

  • 完整性影响:利用程序对软件完整性的影响程度

  • 可用性影响:利用程序对软件可用性的影响程度

CVSS 计算器可在nvd.nist.gov/vuln-metrics/cvss/v3-calculator找到:

图 9.2 - CVSS 计算器

图 9.2 - CVSS 计算器

尽管前面截图中的输入字段只涵盖了基本分数指标,但它们作为决定漏洞严重程度的基本因素。还有另外两个指标可以用来评估漏洞的严重程度,但我们不打算在本节中涵盖它们。根据 CVSS(第 3 版),分数有四个范围:

  • : 0.1-3.9

  • 中等: 4-6.9

  • : 7-8.9

  • 关键: 9-10

通常,图像扫描工具在报告图像中的任何漏洞时会提供 CVSS 分数。在采取任何响应措施之前,漏洞分析至少还有一步。你需要知道漏洞的严重程度也可能受到你自己环境的影响。让我举几个例子:

  • 漏洞只能在 Windows 中被利用,但基本操作系统镜像不是 Windows。

  • 漏洞可以从网络访问中被利用,但图像中的进程只发送出站请求,从不接受入站请求。

上述情景展示了 CVSS 分数并不是唯一重要的因素。你应该专注于那些既关键又相关的漏洞。然而,我们的建议仍然是明智地优先处理漏洞,并尽快修复它们。

如果在图像中发现漏洞,最好及早修复。如果在开发阶段发现漏洞,那么你应该有足够的时间来做出响应。如果在运行生产集群中发现漏洞,应该在补丁可用时立即修补图像并重新部署。如果没有补丁可用,那么有一套缓解策略可以防止集群受到损害。

这就是为什么图像扫描工具对你的 CI/CD 流水线至关重要。在一节中涵盖漏洞管理并不现实,但我认为对漏洞管理的基本理解将帮助你充分利用任何图像扫描工具。有一些流行的开源图像扫描工具可用,比如 Anchore、Clair、Trivvy 等等。让我们看一个这样的图像扫描工具和例子。

使用 Anchore Engine 扫描图像

Anchore Engine 是一个开源的图像扫描工具。它不仅分析 Docker 图像,还允许用户定义接受图像扫描策略。在本节中,我们将首先对 Anchore Engine 进行高层介绍,然后我们将展示如何使用 Anchore 自己的 CLI 工具anchore-cli部署 Anchore Engine 和 Anchore Engine 的基本图像扫描用例。

Anchore Engine 简介

当图像提交给 Anchore Engine 进行分析时,Anchore Engine 将首先从图像注册表中检索图像元数据,然后下载图像并将图像排队进行分析。以下是 Anchore Engine 将要分析的项目:

  • 图像元数据

  • 图像层

  • 操作系统软件包,如debrpmapkg

  • 文件数据

  • 应用程序依赖包:

  • Ruby 宝石

  • Node.js NPMs

  • Java 存档

  • Python 软件包

  • 文件内容

要在 Kubernetes 集群中使用 Helm 部署 Anchore Engine——CNCF 项目,这是 Kubernetes 集群的软件包管理工具,请运行以下命令:

$ helm install anchore-demo stable/anchore-engine

Anchore Engine 由几个微服务组成。在 Kubernetes 集群中部署时,您会发现以下工作负载正在运行:

$ kubectl get deploy
NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
anchore-demo-anchore-engine-analyzer      1/1     1            1           3m37s
anchore-demo-anchore-engine-api           1/1     1            1           3m37s
anchore-demo-anchore-engine-catalog       1/1     1            1           3m37s
anchore-demo-anchore-engine-policy        1/1     1            1           3m37s
anchore-demo-anchore-engine-simplequeue   1/1     1            1           3m37s
anchore-demo-postgresql                   1/1     1            1           3m37s

Anchore Engine 将图像扫描服务解耦为前面日志中显示的微服务:

  • API:接受图像扫描请求

  • 目录:维护图像扫描作业的状态

  • 策略:加载图像分析结果并执行策略评估

  • Analyzer:从图像注册表中拉取图像并执行分析

  • Simplequeue:排队图像扫描任务

  • PostgreSQL:存储图像分析结果和状态

现在 Anchore Engine 已成功部署在 Kubernetes 集群中,让我们看看如何使用anchore-cli进行图像扫描。

使用 anchore-cli 扫描图像

Anchore Engine 支持从 RESTful API 和anchore-cli访问。anchore-cli在迭代使用时非常方便。anchore-cli不需要在 Kubernetes 集群中运行。您需要配置以下环境变量以启用对 Anchore Engine 的 CLI 访问:

  • ANCHORE_CLI_URL:Anchore Engine API 端点

  • ANCHORE_CLI_USER:访问 Anchore Engine 的用户名

  • ANCHORE_CLI_PASS:访问 Anchore Engine 的密码

一旦您成功配置了环境变量,您可以使用以下命令验证与 Anchore Engine 的连接:

root@anchore-cli:/# anchore-cli system status

输出应该如下所示:

Service analyzer (anchore-demo-anchore-engine-analyzer-5fd777cfb5-jtqp2, http://anchore-demo-anchore-engine-analyzer:8084): up
Service apiext (anchore-demo-anchore-engine-api-6dd475cf-n24xb, http://anchore-demo-anchore-engine-api:8228): up
Service policy_engine (anchore-demo-anchore-engine-policy-7b8f68fbc-q2dm2, http://anchore-demo-anchore-engine-policy:8087): up
Service simplequeue (anchore-demo-anchore-engine-simplequeue-6d4567c7f4-7sll5, http://anchore-demo-anchore-engine-simplequeue:8083): up
Service catalog (anchore-demo-anchore-engine-catalog-949bc68c9-np2pc, http://anchore-demo-anchore-engine-catalog:8082): up
Engine DB Version: 0.0.12
Engine Code Version: 0.6.1

anchore-cli 能够与 Kubernetes 集群中的 Anchore Engine 进行通信。现在让我们使用以下命令扫描一个镜像:

root@anchore-cli:/# anchore-cli image add kaizheh/nginx-docker

输出应该如下所示:

Image Digest: sha256:416b695b09a79995b3f25501bf0c9b9620e82984132060bf7d66d877 6c1554b7
Parent Digest: sha256:416b695b09a79995b3f25501bf0c9b9620e82984132060bf7d66d877 6c1554b7
Analysis Status: analyzed
Image Type: docker
Analyzed At: 2020-03-22T05:48:14Z
Image ID: bcf644d78ccd89f36f5cce91d205643a47c8a5277742c5b311c9d9 6699a3af82
Dockerfile Mode: Guessed
Distro: debian
Distro Version: 10
Size: 1172316160
Architecture: amd64
Layer Count: 16
Full Tag: docker.io/kaizheh/nginx-docker:latest
Tag Detected At: 2020-03-22T05:44:38Z

您将从镜像中获得镜像摘要、完整标签等信息。根据镜像大小,Anchore Engine 分析镜像可能需要一些时间。一旦分析完成,您将看到 Analysis Status 字段已更新为 analyzed。使用以下命令检查镜像扫描状态:

root@anchore-cli:/# anchore-cli image get kaizheh/nginx-docker

输出应该如下所示:

Image Digest: sha256:416b695b09a79995b3f25501bf0c9b9620e82984132060bf7d66d877 6c1554b7
Parent Digest: sha256:416b695b09a79995b3f25501bf0c9b9620e82984132060bf7d66d877 6c1554b7
Analysis Status: analyzed
Image Type: docker
Analyzed At: 2020-03-22T05:48:14Z
Image ID: bcf644d78ccd89f36f5cce91d205643a47c8a5277742c5b311c9d96699a3a f82
Dockerfile Mode: Guessed
Distro: debian
Distro Version: 10
Size: 1172316160
Architecture: amd64
Layer Count: 16
Full Tag: docker.io/kaizheh/nginx-docker:latest
Tag Detected At: 2020-03-22T05:44:38Z

我们之前简要提到了 Anchore Engine 策略;Anchore Engine 策略允许您根据漏洞的严重程度不同定义规则来处理漏洞。在默认的 Anchore Engine 策略中,您将在默认策略中找到以下两条规则。第一条规则如下:

{
	"action": "WARN",
	"gate": "vulnerabilities",
	"id": "6063fdde-b1c5-46af-973a-915739451ac4",
	"params": [{
			"name": "package_type",
			"value": "all"
		},
		{
			"name": "severity_comparison",
			"value": "="
		},
		{
			"name": "severity",
			"value": "medium"
		}
	],
	"trigger": "package"
},

第一条规则定义了任何具有中等级漏洞的软件包仍将将策略评估结果设置为通过。第二条规则如下:

 {
 	"action": "STOP",
 	"gate": "vulnerabilities",
 	"id": "b30e8abc-444f-45b1-8a37-55be1b8c8bb5",
 	"params": [{
 			"name": "package_type",
 			"value": "all"
 		},
 		{
 			"name": "severity_comparison",
 			"value": ">"
 		},
 		{
 			"name": "severity",
 			"value": "medium"
 		}
 	],
 	"trigger": "package"
 },

第二条规则定义了任何具有高或关键漏洞的软件包将会将策略评估结果设置为失败。镜像分析完成后,使用以下命令检查策略:

root@anchore-cli:/# anchore-cli --json evaluate check sha256:416b695b09a79995b3f25501bf0c9b9620e82984132060bf7d66d877 6c1554b7 --tag docker.io/kaizheh/nginx-docker:latest

输出应该如下所示:

[
    {
        "sha256:416b695b09a79995b3f25501bf0c9b9620e82984132060 bf7d66d8776c1554b7": {
            "docker.io/kaizheh/nginx-docker:latest": [
                {
                    "detail": {},
                    "last_evaluation": "2020-03-22T06:19:44Z",
                    "policyId": "2c53a13c-1765-11e8-82ef-235277 61d060",
                    "status": "fail"
                }
            ]
        }
    }
]

因此,镜像 docker.io/kaizheh/nginx-docker:latest 未通过默认策略评估。这意味着必须存在一些高或关键级别的漏洞。使用以下命令列出镜像中的所有漏洞:

root@anchore-cli:/# anchore-cli image vuln docker.io/kaizheh/nginx-docker:latest all

输出应该如下所示:

Vulnerability ID        Package                                                Severity          Fix                              CVE Refs                Vulnerability URL
CVE-2019-9636           Python-2.7.16                                          Critical          None                             CVE-2019-9636           https://nvd.nist.gov/vuln/detail/CVE-2019-9636
CVE-2020-7598           minimist-0.0.8                                         Critical          None                             CVE-2020-7598           https://nvd.nist.gov/vuln/detail/CVE-2020-7598
CVE-2020-7598           minimist-1.2.0                                         Critical          None                             CVE-2020-7598           https://nvd.nist.gov/vuln/detail/CVE-2020-7598
CVE-2020-8116           dot-prop-4.2.0                                         Critical          None                             CVE-2020-8116           https://nvd.nist.gov/vuln/detail/CVE-2020-8116
CVE-2013-1753           Python-2.7.16                                          High              None                             CVE-2013-1753           https://nvd.nist.gov/vuln/detail/CVE-2013-1753
CVE-2015-5652           Python-2.7.16                                          High              None                             CVE-2015-5652           https://nvd.nist.gov/vuln/detail/CVE-2015-5652
CVE-2019-13404          Python-2.7.16                                          High              None                             CVE-2019-13404          https://nvd.nist.gov/vuln/detail/CVE-2019-13404
CVE-2016-8660           linux-compiler-gcc-8-x86-4.19.67-2+deb10u1             Low               None                             CVE-2016-8660           https://security-tracker.debian.org/tracker/CVE-2016-8660
CVE-2016-8660           linux-headers-4.19.0-6-amd64-4.19.67-2+deb10u1         Low               None                             CVE-2016-8660           https://security-tracker.debian.org/tracker/CVE-2016-8660

上述列表显示了镜像中的所有漏洞,包括 CVE ID、软件包名称、严重程度、是否有修复可用以及参考信息。Anchore Engine 策略基本上帮助您过滤掉较不严重的漏洞,以便您可以专注于更严重的漏洞。然后,您可以开始与安全团队进行漏洞分析。

注意

有时,如果一个软件包或库中的高级或关键级别漏洞没有修复可用,您应该寻找替代方案,而不是继续使用有漏洞的软件包。

在接下来的部分,我们将讨论如何将镜像扫描集成到 CI/CD 流水线中。

将镜像扫描集成到 CI/CD 流水线中

镜像扫描可以在 DevOps 流水线的多个阶段触发,我们已经讨论了在流水线早期阶段扫描镜像的优势。然而,新的漏洞将被发现,您的漏洞数据库应该不断更新。这表明,即使在构建阶段通过了镜像扫描,也不意味着在运行时阶段会通过,如果发现了新的关键漏洞,并且该漏洞也存在于镜像中。当发生这种情况时,您应该停止工作负载部署,并相应地应用缓解策略。在深入集成之前,让我们看一下适用于镜像扫描的 DevOps 阶段的大致定义:

  • 构建: 当镜像在 CI/CD 流水线中构建时

  • 部署: 当镜像即将部署到 Kubernetes 集群时

  • 运行时: 在镜像部署到 Kubernetes 集群并且容器正在运行时

虽然有许多不同的 CI/CD 流水线和许多不同的镜像扫描工具供您选择,但整合镜像扫描到 CI/CD 流水线中的概念是确保 Kubernetes 工作负载和 Kubernetes 集群的安全。

构建阶段的扫描

有许多 CI/CD 工具,例如 Jenkins、Spinnaker 和 Screwdriver,供您使用。在本节中,我们将展示如何将镜像扫描集成到 GitHub 工作流程中。GitHub 中的工作流程是一个可配置的自动化流程,包含多个作业。这类似于 Jenkins 流水线的概念,但是以 YAML 格式定义。具有镜像扫描的简单工作流程就像定义触发器。通常在拉取请求或提交推送时完成,设置构建环境,例如 Ubuntu。

然后在工作流程中定义步骤:

  1. 检出 PR 分支。

  2. 从分支构建镜像。

  3. 将镜像推送到注册表-这是可选的。当本地构建镜像时,应该能够启动镜像扫描器来扫描镜像。

  4. 扫描新构建或推送的镜像。

  5. 如果违反策略,则失败工作流。

以下是 GitHub 中定义的示例工作流程:

name: CI
...
  build:
    runs-on: ubuntu-latest
    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
    - uses: actions/checkout@v2
    # Runs a set of commands using the runners shell
    - name: Build and Push
      env:
        DOCKER_SECRET: ${{ secrets.DOCKER_SECRET }} 
      run: |
        cd master/chapter9 && echo "Build Docker Image"
        docker login -u kaizheh -p ${DOCKER_SECRET}
        docker build -t kaizheh/anchore-cli . && docker push kaizheh/anchore-cli
    - name: Scan
      env:
        ANCHORE_CLI_URL: ${{ secrets.ANCHORE_CLI_URL }} 
        ANCHORE_CLI_USER:  ${{ secrets.ANCHORE_CLI_USER }}
        ANCHORE_CLI_PASS:  ${{ secrets.ANCHORE_CLI_PASS }}
      run: |      
        pip install anchorecli            # install anchore-cli
        export PATH="$HOME/.local/bin/:$PATH"       
        img="kaizheh/anchore-cli"
        anchore-cli image add $img        # add image
        sha=$(anchore-cli --json --api-version=0.2.4 image get $img | jq .[0].imageDigest -r)                   # get sha value
        anchore-cli image wait $img       # wait for image analyzed
        anchore-cli --json evaluate check $sha --tag $img # evaluate       
    - name: Post Scan
      run: |
        # Slack to notify developers scan result or invite new reviewer if failed
        exit 1  # purposely ends here

在构建流水线的第一步中,我使用了checkout GitHub 操作来检出分支。GitHub 操作对于工作流程就像编程语言中的函数一样。它封装了您不需要知道的细节,但为您执行任务。它可以接受输入参数并返回结果。在第二步中,我们运行了一些命令来构建图像kaizheh/anchore-cli并将图像推送到注册表。在第三步中,我们使用anchore-cli来扫描图像(是的,我们使用 Anchore Engine 来扫描我们自己的anchore-cli图像)。

请注意,我配置了 GitHub secrets 来存储诸如 Docker Hub 访问令牌、Anchore 用户名和密码等敏感信息。在最后一步,我们故意失败以进行演示。但通常,最后一步会随着评论建议的图像扫描结果而带来通知和响应。您将在 GitHub 中找到工作流程的结果详细信息,如下所示:

图 9.3 – GitHub 图像扫描工作流程

图 9.3 – GitHub 图像扫描工作流程

前面的屏幕截图显示了工作流程中每个步骤的状态,当您点击进入时,您将找到每个步骤的详细信息。Anchore 还提供了一个名为 Anchore Container Scan 的图像扫描 GitHub 操作。它在新构建的图像上启动 Anchore Engine 扫描程序,并返回漏洞、清单和可以用于失败构建的通过/失败策略评估。

在部署阶段进行扫描

尽管部署是一个无缝的过程,但我想在一个单独的部分提出关于在部署阶段进行图像扫描的两个原因:

  • 将应用程序部署到 Kubernetes 集群时,可能会发现新的漏洞,即使它们在构建时通过了图像扫描检查。最好在它们在 Kubernetes 集群中运行时发现漏洞之前阻止它们。

  • 图像扫描可以成为 Kubernetes 中验证准入过程的一部分。

我们已经在第七章中介绍了ValidatingAdmissionWebhook的概念,身份验证、授权和准入控制。现在,让我们看看图像扫描如何帮助通过在 Kubernetes 集群中运行之前扫描其图像来验证工作负载。图像扫描准入控制器是来自 Sysdig 的开源项目。它扫描即将部署的工作负载中的图像。如果图像未通过图像扫描策略,工作负载将被拒绝。以下是工作流程图:

图 9.4 - 图像扫描准入工作流

图 9.4 - 图像扫描准入工作流

前面的图表显示了基于图像扫描验证的工作负载准入过程:

  1. 有一个工作负载创建请求发送到kube-apiserver

  2. kube-apiserver根据验证 webhook 配置将请求转发到注册的验证 webhook 服务器。

  3. 验证 webhook 服务器从工作负载的规范中提取图像信息,并将其发送到 Anchore Engine API 服务器。

  4. 根据图像扫描策略,Anchore Engine 将验证决定作为验证决定返回给服务器。

  5. 验证 webhook 服务器将验证决定转发给kube-apiserver

  6. kube-apiserver根据来自图像扫描策略评估结果的验证决定,要么允许要么拒绝工作负载。

要部署图像扫描准入控制器,首先要检出 GitHub 存储库(github.com/sysdiglabs/image-scanning-admission-controller),然后运行以下命令:

$ make deploy

然后你应该找到 webhook 服务器和服务已经创建:

NAME                                              READY   STATUS    RESTARTS   AGE
pod/image-scan-k8s-webhook-controller-manager-0   1/1     Running   1          16s
NAME                                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/image-scan-k8s-webhook-controller-manager-service   ClusterIP   100.69.225.172   <none>        443/TCP   16s
service/webhook-server-service                              ClusterIP   100.68.111.117   <none>        443/TCP   8s
NAME                                                         READY   AGE
statefulset.apps/image-scan-k8s-webhook-controller-manager   1/1     16s

除了 webhook 服务器部署,该脚本还创建了一个ValidatingWebhookConfiguration对象来注册图像扫描准入 webhook 服务器,该对象在generic-validatingewebhookconfig.yaml中定义到kube-apiserver

apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: validating-webhook-configuration
webhooks:
- name: validating-create-pods.k8s.io
  clientConfig:
    service:
      namespace: image-scan-k8s-webhook-system
      name: webhook-server-service
      path: /validating-create-pods
    caBundle: {{CA_BUNDLE}}
  rules:
  - operations:
    - CREATE
    apiGroups:
    - ""
    apiVersions:
    - "v1"
    resources:
    - pods
  failurePolicy: Fail

验证 webhook 配置对象基本上告诉kube-apiserver将任何 pod 创建请求转发到image-scan-webhook-system命名空间中的webhook-server-service,并使用/validating-create-pod URL 路径。

您可以使用图像扫描准入控制器提供的测试用例来验证您的设置,如下所示:

$ make test

在测试中,将在 Kubernetes 集群中部署三个不同的 pod。其中一个存在关键漏洞,违反了图像扫描策略。因此,具有关键漏洞的工作负载将被拒绝。

+ kubectl run --image=bitnami/nginx --restart=Never nginx
pod/nginx created
+ kubectl run --image=kaizheh/apache-struts2-cve-2017-5638 --restart=Never apache-struts2
Error from server (Image failed policy check: kaizheh/apache-struts2-cve-2017-5638): admission webhook "validating-create-pods.k8s.io" denied the request: Image failed policy check: kaizheh/apache-struts2-cve-2017-5638
+ kubectl run --image=alpine:3.2 --restart=Never alpine
pod/alpine created

前面的输出显示,带有图像kaizheh/apache-struts2-cve-2017-5638的工作负载被拒绝了。该图像运行 Apache Struts 2 服务,其中包含一个 CVSS 评分为 10 的关键漏洞(nvd.nist.gov/vuln/detail/CVE-2017-5638)。尽管测试中的 CVE 是旧的,但您应该能够在早期发现它。然而,新的漏洞将被发现,漏洞数据库将不断更新。为即将部署在 Kubernetes 集群中的任何工作负载设置一个门卫是至关重要的。图像扫描作为验证入场是 Kubernetes 部署的一个良好安全实践。现在,让我们谈谈在 Kubernetes 集群中运行时阶段的图像扫描。

运行时阶段的扫描

干得好!工作负载的图像在构建和部署阶段通过了图像扫描策略评估。但这并不意味着图像没有漏洞。请记住,新的漏洞将被发现。通常,图像扫描器使用的漏洞数据库将每隔几个小时更新一次。一旦漏洞数据库更新,您应该触发图像扫描器扫描在 Kubernetes 集群中正在运行的图像。有几种方法可以做到这一点:

  • 直接在每个工作节点上扫描拉取的图像。要在工作节点上扫描图像,您可以使用诸如 Sysdig 的secure-inline-scan工具(github.com/sysdiglabs/secure-inline-scan)。

  • 定期在注册表中扫描图像,直接在漏洞数据库更新后进行扫描。

再次强调,一旦发现正在使用的图像中存在重大漏洞,您应该修补易受攻击的图像并重新部署,以减少攻击面。

总结

在本章中,我们首先简要讨论了容器图像和漏洞。然后,我们介绍了一个开源图像扫描工具 Anchore Engine,并展示了如何使用anchore-cli进行图像扫描。最后但同样重要的是,我们讨论了如何将图像扫描集成到 CI/CD 流水线的三个不同阶段:构建、部署和运行时。图像扫描在保护 DevOps 流程方面表现出了巨大的价值。一个安全的 Kubernetes 集群需要保护整个 DevOps 流程。

现在,您应该可以轻松部署 Anchore Engine 并使用anchore-cli来触发图像扫描。一旦您在图像中发现任何漏洞,请使用 Anchore Engine 策略将其过滤掉,并了解其真正影响。我知道这需要时间,但在您的 CI/CD 流水线中设置图像扫描是必要且很棒的。通过这样做,您将使您的 Kubernetes 集群更加安全。

在下一章中,我们将讨论 Kubernetes 集群中的资源管理和实时监控。

问题

让我们使用一些问题来帮助您更好地理解本章内容:

  1. 哪个 Docker 命令可以用来列出图像文件层?

  2. 根据 CVSS3 标准,哪个漏洞评分范围被认为是高风险的?

  3. anchore-cli命令是什么,用于开始扫描图像?

  4. anchore-cli命令是什么,用于列出图像的漏洞?

  5. anchore-cli命令是什么,用于评估符合 Anchore Engine 策略的图像?

  6. 为什么将图像扫描集成到 CI/CD 流水线中如此重要?

进一步参考

第十章:Kubernetes 集群的实时监控和资源管理

服务的可用性是机密性、完整性和可用性CIA)三要素中的关键组成部分之一。曾经有许多恶意攻击者使用不同的技术来破坏用户服务的可用性。一些对关键基础设施的攻击,如电力网络和银行,导致了经济上的重大损失。其中最显著的攻击之一是对亚马逊 AWS Route 53 基础设施的攻击,导致全球核心 IT 服务中断。为了避免这样的问题,基础设施工程师实时监控资源使用和应用程序健康状况,以确保组织提供的服务的可用性。实时监控通常与警报系统相结合,当观察到服务中断的症状时通知利益相关者。

在本章中,我们将讨论如何确保 Kubernetes 集群中的服务始终正常运行。我们将首先讨论单体环境中的监控和资源管理。接下来,我们将讨论资源请求和资源限制,这是 Kubernetes 资源管理的核心概念。然后,我们将看看 Kubernetes 提供的诸如LimitRanger之类的工具,用于资源管理,然后将重点转移到资源监控。我们将研究内置监视器,如 Kubernetes 仪表板和 Metrics Server。最后,我们将研究一些开源工具,如 Prometheus 和 Grafana,用于监视 Kubernetes 集群的状态。

在本章中,我们将讨论以下内容:

  • 在单体环境中进行实时监控和管理

  • 在 Kubernetes 中管理资源

  • 在 Kubernetes 中监控资源

在单体环境中进行实时监控和管理

资源管理和监控在单体环境中同样很重要。在单体环境中,基础设施工程师经常将 Linux 工具(如topntophtop)的输出导入数据可视化工具,以监视虚拟机的状态。在托管环境中,内置工具如 Amazon CloudWatch 和 Azure 资源管理器有助于监视资源使用情况。

除了资源监控之外,基础设施工程师还会主动为进程和其他实体分配最低资源需求和使用限制。这确保了服务有足够的资源可用。此外,资源管理还确保不良行为或恶意进程不会占用资源并阻止其他进程工作。对于单体部署,诸如 CPU、内存和生成的进程等资源会被限制在不同的进程中。在 Linux 上,可以使用prlimit来限制进程的限制:

$prlimit --nproc=2 --pid=18065

这个命令设置了父进程可以生成的子进程的限制为2。设置了这个限制后,如果一个 PID 为18065的进程尝试生成超过2个子进程,它将被拒绝。

与单体环境类似,Kubernetes 集群运行多个 pod、部署和服务。如果攻击者能够生成 Kubernetes 对象,比如 pod 或部署,攻击者可以通过耗尽 Kubernetes 集群中可用的资源来发动拒绝服务攻击。如果没有足够的资源监控和资源管理,集群中运行的服务不可用可能会对组织造成经济影响。

在 Kubernetes 中管理资源

Kubernetes 提供了主动分配和限制 Kubernetes 对象可用资源的能力。在本节中,我们将讨论资源请求和限制,这构成了 Kubernetes 中资源管理的基础。接下来,我们将探讨命名空间资源配额和限制范围。使用这两个功能,集群管理员可以限制不同 Kubernetes 对象可用的计算和存储资源。

资源请求和限制

正如我们在第一章中讨论的那样,Kubernetes 架构中,默认的调度程序是kube-scheduler,它运行在主节点上。kube-scheduler会找到最适合的节点来运行未调度的 pod。它通过根据 pod 请求的存储和计算资源来过滤节点来实现这一点。如果调度程序无法为 pod 找到节点,pod 将保持在挂起状态。此外,如果节点的所有资源都被 pod 利用,节点上的kubelet将清理死掉的 pod - 未使用的镜像。如果清理不能减轻压力,kubelet将开始驱逐那些消耗更多资源的 pod。

资源请求指定了 Kubernetes 对象保证获得的资源。不同的 Kubernetes 变体或云提供商对资源请求有不同的默认值。可以在工作负载的规范中指定 Kubernetes 对象的自定义资源请求。资源请求可以针对 CPU、内存和 HugePages 进行指定。让我们看一个资源请求的例子。

让我们创建一个没有在 yaml 规范中指定资源请求的 Pod,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  containers:
  - name: demo

Pod 将使用部署的默认资源请求:

$kubectl get pod demo —output=yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"demo","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"demo"}]}}
    kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu request for container
      demo'
  creationTimestamp: "2020-05-07T21:54:47Z"
  name: demo
  namespace: default
  resourceVersion: "3455"
  selfLink: /api/v1/namespaces/default/pods/demo
  uid: 5e783495-90ad-11ea-ae75-42010a800074
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: demo
    resources:
      requests:
        cpu: 100m

对于前面的例子,Pod 的默认资源请求是 0.1 CPU 核心。现在让我们向 .yaml 规范中添加一个资源请求并看看会发生什么:

apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  containers:
  - name: demo
    image: nginx
    resources:
      limits:
          hugepages-2Mi: 100Mi
      requests:
        cpu: 500m         memory: 300Mi         hugepages-2Mi: 100Mi 

这个规范创建了一个具有 0.5 CPU 核心、300 MB 和 hugepages-2Mi 的 100 MB 的资源请求的 Pod。您可以使用以下命令检查 Pod 的资源请求:

$kubectl get pod demo —output=yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-05-07T22:02:16Z"
  name: demo-1
  namespace: default
  resourceVersion: "5030"
  selfLink: /api/v1/namespaces/default/pods/demo-1
  uid: 6a276dd2-90ae-11ea-ae75-42010a800074
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: demo
    resources:
      limits:
        hugepages-2Mi: 100Mi
      requests:
        cpu: 500m
        hugepages-2Mi: 100Mi
        memory: 300Mi

从输出中可以看出,Pod 使用了 0.5 CPU 核心、300 MB 内存 和 100 MB 2 MB hugepages 的自定义资源请求,而不是默认的 1 MB。

另一方面,限制是 Pod 可以使用的资源的硬限制。限制指定了 Pod 应该被允许使用的最大资源。如果需要的资源超过了限制中指定的资源,Pod 将受到限制。与资源请求类似,您可以为 CPU、内存和 HugePages 指定限制。让我们看一个限制的例子:

$ cat stress.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  containers:
  - name: demo
    image: polinux/stress
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

这个 Pod 启动一个尝试在启动时分配 150M 内存的压力进程。如果 .yaml 规范中没有指定限制,Pod 将可以正常运行:

$ kubectl create -f stress.yaml pod/demo created
$ kubectl get pods NAME         READY   STATUS             RESTARTS   AGE demo         1/1     Running            0          3h

限制被添加到 Pod 的 yaml 规范的容器部分:

containers:
  - name: demo
    image: polinux/stress
    resources:
      limits:
        memory: "150Mi"
    command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

压力进程无法运行,Pod 进入 CrashLoopBackOff 状态:

$ kubectl get pods
NAME     READY   STATUS             RESTARTS   AGE
demo     1/1     Running            0          44s
demo-1   0/1     CrashLoopBackOff   1          5s

当您描述 Pod 时,可以看到 Pod 被终止并出现 OOMKilled 错误:

$ kubectl describe pods demo
Name:         demo
Namespace:    default
...
Containers:
  demo:
    Container ID:  docker://a43de56a456342f7d53fa9752aa4fa7366 cd4b8c395b658d1fc607f2703750c2
    Image:         polinux/stress
    Image ID:      docker-pullable://polinux/stress@sha256:b61 44f84f9c15dac80deb48d3a646b55c7043ab1d83ea0a697c09097aaad21aa
...
    Command:
      stress
    Args:
      --vm
      1
      --vm-bytes
      150M
      --vm-hang
      1
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    1
      Started:      Mon, 04 May 2020 10:48:14 -0700
      Finished:     Mon, 04 May 2020 10:48:14 -0700

资源请求和限制被转换、映射到 docker 参数———cpu-shares—memory 标志——并传递给容器运行时。

我们看了资源请求和限制如何为 Pod 工作的例子,但是相同的例子也适用于 DaemonSet、Deployments 和 StatefulSets。接下来,我们将看一下命名空间资源配额如何帮助设置命名空间可以使用的资源的上限。

命名空间资源配额

命名空间的资源配额有助于定义命名空间内所有对象可用的资源请求和限制。使用资源配额,您可以限制以下内容:

  • request.cpu:命名空间中所有对象的 CPU 的最大资源请求。

  • request.memory:命名空间中所有对象的内存的最大资源请求。

  • limit.cpu:命名空间中所有对象的 CPU 的最大资源限制。

  • limit.memory:命名空间中所有对象的内存的最大资源限制。

  • requests.storage:命名空间中存储请求的总和不能超过这个值。

  • count:资源配额也可以用来限制集群中不同 Kubernetes 对象的数量,包括 pod、服务、PersistentVolumeClaims 和 ConfigMaps。

默认情况下,云提供商或不同的变体对命名空间应用了标准限制。在Google Kubernetes EngineGKE)上,cpu请求被设置为 0.1 CPU 核心:

$ kubectl describe namespace default
Name:         default
Labels:       <none>
Annotations:  <none>
Status:       Active
Resource Quotas
 Name:                       gke-resource-quotas
 Resource                    Used  Hard
 --------                    ---   ---
 count/ingresses.extensions  0     100
 count/jobs.batch            0     5k
 pods                        2     1500
 services                    1     500
Resource Limits
 Type       Resource  Min  Max  Default Request  Default Limit  Max Limit/Request Ratio
 ----       --------  ---  ---  ---------------  -------------  -----------------------
 Container  cpu       -    -    100m             -              -

让我们看一个例子,当资源配额应用到一个命名空间时会发生什么:

  1. 创建一个命名空间演示:
$ kubectl create namespace demo
namespace/demo created
  1. 定义一个资源配额。在这个例子中,配额将 CPU 的资源请求限制为1 CPU:
$ cat quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
spec:
  hard:
    requests.cpu: "1"
  1. 通过以下命令将配额应用到命名空间:
$ kubectl apply -f quota.yaml --namespace demo
resourcequota/compute-resources created
  1. 您可以通过执行以下命令来检查资源配额是否成功应用到命名空间:
$ kubectl describe namespace demo
Name:         demo
Labels:       <none>
Annotations:  <none>
Status:       Active
Resource Quotas
 Name:         compute-resources
 Resource      Used  Hard
 --------      ---   ---
 requests.cpu  0     1
 Name:                       gke-resource-quotas
 Resource                    Used  Hard
 --------                    ---   ---
 count/ingresses.extensions  0     100
 count/jobs.batch            0     5k
 pods                        0     1500
 services                    0     500
  1. 现在,如果我们尝试创建使用1 CPU 的两个 pod,第二个请求将失败,并显示以下错误:
$ kubectl apply -f nginx-cpu-1.yaml --namespace demo
Error from server (Forbidden): error when creating "nginx-cpu-1.yaml": pods "demo-1" is forbidden: exceeded quota: compute-resources, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1

资源配额确保了命名空间中 Kubernetes 对象的服务质量。

LimitRanger

我们在第七章中讨论了LimitRanger准入控制器,身份验证、授权和准入控制。集群管理员可以利用限制范围来确保行为不端的 pod、容器或PersistentVolumeClaims不会消耗所有可用资源。

要使用限制范围,启用LimitRanger准入控制器:

$ ps aux | grep kube-api
root      3708  6.7  8.7 497216 345256 ?       Ssl  01:44   0:10 kube-apiserver --advertise-address=192.168.99.116 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota

使用 LimitRanger,我们可以对存储和计算资源强制执行defaultminmax限制。集群管理员为诸如 pod、容器和 PersistentVolumeClaims 等对象创建一个限制范围。对于任何对象创建或更新的请求,LimitRanger 准入控制器会验证请求是否违反了任何限制范围。如果请求违反了任何限制范围,将发送 403 Forbidden 响应。

让我们看一个简单限制范围的例子:

  1. 创建一个将应用限制范围的命名空间:
$kubectl create namespace demo
  1. 为命名空间定义一个LimitRange
$ cat limit_range.yaml
apiVersion: "v1"
kind: "LimitRange"
metadata:
  name: limit1
  namespace: demo
spec:
  limits:
  - type: "Container"
    max:
      memory: 512Mi
      cpu: 500m
    min:
      memory: 50Mi
      cpu: 50m
  1. 验证limitrange是否被应用:
$ kubectl get limitrange -n demo
NAME     CREATED AT
limit1   2020-04-30T02:06:18Z
  1. 创建一个违反限制范围的 pod:
$cat nginx-bad.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-bad
spec:
  containers:
  - name: nginx-bad
    image: nginx-bad
    resources:
      limits:
        memory: "800Mi"
        cpu: "500m"

这个请求将被拒绝:

$ kubectl apply -f nginx-bad.yaml -n demo
Error from server (Forbidden): error when creating "nginx-bad.yaml": pods "nginx-bad" is forbidden: maximum memory usage per Container is 512Mi, but limit is 800M

如果 LimitRanger 指定了 CPU 或内存,所有的 pod 和容器都应该有 CPU 或内存的请求或限制。LimitRanger 在 API 服务器接收到创建或更新对象的请求时起作用,但在运行时不起作用。如果一个 pod 在限制被应用之前就违反了限制,它将继续运行。理想情况下,限制应该在命名空间创建时应用。

现在我们已经看了一些可以用于积极资源管理的功能,我们转而看一些可以帮助我们监控集群并在事态恶化之前通知我们的工具。

监控 Kubernetes 资源

正如我们之前讨论的,资源监控是确保集群中服务可用性的重要步骤。资源监控可以发现集群中服务不可用的早期迹象或症状。资源监控通常与警报管理相结合,以确保利益相关者在观察到集群中出现任何问题或与任何问题相关的症状时尽快收到通知。

在这一部分,我们首先看一些由 Kubernetes 提供的内置监视器,包括 Kubernetes Dashboard 和 Metrics Server。我们将看看如何设置它,并讨论如何有效地使用这些工具。接下来,我们将看一些可以插入到您的 Kubernetes 集群中并提供比内置工具更深入的洞察力的开源工具。

内置监视器

让我们来看一些由 Kubernetes 提供的用于监控 Kubernetes 资源和对象的工具 - Metrics Server 和 Kubernetes Dashboard。

Kubernetes Dashboard

Kubernetes Dashboard 为集群管理员提供了一个 Web UI,用于创建、管理和监控集群对象和资源。集群管理员还可以使用仪表板创建 pod、服务和 DaemonSets。仪表板显示了集群的状态和集群中的任何错误。

Kubernetes 仪表板提供了集群管理员在集群中管理资源和对象所需的所有功能。鉴于仪表板的功能,应该将对仪表板的访问限制在集群管理员范围内。从 v1.7.0 版本开始,仪表板具有登录功能。2018 年,仪表板中发现了一个特权升级漏洞(CVE-2018-18264),允许未经身份验证的用户登录到仪表板。对于这个问题,尚无已知的野外利用,但这个简单的漏洞可能会对许多 Kubernetes 发行版造成严重破坏。

当前的登录功能允许使用服务账户和 kubeconfig 登录。建议使用服务账户令牌来访问 Kubernetes 仪表板:

图 10.1 – Kubernetes 仪表板

图 10.1 – Kubernetes 仪表板

为了允许服务账户使用 Kubernetes 仪表板,您需要将 cluster-admin 角色添加到服务账户中。让我们看一个示例,说明如何使用服务账户来访问 Kubernetes 仪表板:

  1. 在默认命名空间中创建一个服务账户:
$kubectl create serviceaccount dashboard-admin-sa
  1. cluster-admin 角色与服务账户关联:
$kubectl create clusterrolebinding dashboard-admin-sa --clusterrole=cluster-admin --serviceaccount=default:dashboard-admin-sa
  1. 获取服务账户的令牌:
$ kubectl describe serviceaccount dashboard-admin-sa
Name:                dashboard-admin-sa
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   dashboard-admin-sa-token-5zwpw
Tokens:              dashboard-admin-sa-token-5zwpw
Events:              <none>
  1. 使用以下命令获取服务账户的令牌:
$ kubectl describe secrets dashboard-admin-sa-token-5zwpw
Name:         dashboard-admin-sa-token-5zwpw
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: dashboard-admin-sa
              kubernetes.io/service-account.uid: 83218a92-915c-11ea-b763-42010a800022
Type:  kubernetes.io/service-account-token
Data
====
ca.crt:     1119 bytes
namespace:  7 bytes
token:      <token>
  1. 使用服务账户令牌登录到仪表板:

图 10.2 – Kubernetes 仪表板登录

图 10.2 – Kubernetes 仪表板登录

使用 Kubernetes 仪表板,管理员可以了解资源可用性、资源分配、Kubernetes 对象和事件日志:

图 10.3 – Kubernetes 仪表板 – 资源分配

图 10.3 – Kubernetes 仪表板 – 资源分配

上述截图显示了节点上资源请求和限制的资源分配情况。以下截图突出显示了 Kubernetes 仪表板上节点的事件:

图 10.4 – Kubernetes 仪表板 – 事件日志

图 10.4 – Kubernetes 仪表板 – 事件日志

Kubernetes 仪表板作为一个容器在主节点上运行。您可以通过枚举主节点上的 Docker 容器来查看这一点:

$ docker ps | grep dashboard
a963e6e6a54b        3b08661dc379           "/metrics-sidecar"       4 minutes ago       Up 4 minutes                            k8s_dashboard-metrics-scraper_dashboard-metrics-scraper-84bfdf55ff-wfxdm_kubernetes-dashboard_5a7ef2a8-b3b4-4e4c-ae85-11cc8b61c1c1_0
c28f0e2799c1        cdc71b5a8a0e           "/dashboard --insecu…"   4 minutes ago       Up 4 minutes                            k8s_kubernetes-dashboard_kubernetes-dashboard-bc446cc64-czmn8_kubernetes-dashboard_40630c71-3c6a-447b-ae68-e23603686ede_0
10f0b024a13f        k8s.gcr.io/pause:3.2   "/pause"                 4 minutes ago       Up 4 minutes                            k8s_POD_dashboard-metrics-scraper-84bfdf55ff-wfxdm_kubernetes-dashboard_5a7ef2a8-b3b4-4e4c-ae85-11cc8b61c1c1_0
f9c1e82174d8        k8s.gcr.io/pause:3.2   "/pause"                 4 minutes ago       Up 4 minutes                            k8s_POD_kubernetes-dashboard-bc446cc64-czmn8_kubernetes-dashboard_40630c71-3c6a-447b-ae68-e23603686ede_0

仪表板进程在主节点上以一组参数运行:

$ ps aux | grep dashboard
dbus     10727  0.9  1.1 136752 46240 ?        Ssl  05:46   0:02 /dashboard --insecure-bind-address=0.0.0.0 --bind-address=0.0.0.0 --namespace=kubernetes-dashboard --enable-skip-login --disable-settings-authorizer
docker   11889  0.0  0.0  11408   556 pts/0    S+   05:51   0:00 grep dashboard

确保仪表板容器使用以下参数运行:

  • 禁用不安全端口--insecure-port允许 Kubernetes 仪表板接收 HTTP 请求。确保在生产环境中禁用它。

  • 禁用不安全的地址:应禁用--insecure-bind-address,以避免 Kubernetes 仪表板可以通过 HTTP 访问的情况。

  • 将地址绑定到本地主机--bind-address应设置为127.0.0.1,以防止主机通过互联网连接。

  • 启用 TLS:使用tls-cert-filetls-key-file来通过安全通道访问仪表板。

  • 确保启用令牌身份验证模式:可以使用--authentication-mode标志指定身份验证模式。默认情况下,它设置为token。确保仪表板不使用基本身份验证。

  • 禁用不安全登录:当仪表板可以通过 HTTP 访问时,会使用不安全登录。这应该默认禁用。

  • 禁用跳过登录:跳过登录允许未经身份验证的用户访问 Kubernetes 仪表板。--enable-skip-login启用跳过登录;这在生产环境中不应存在。

  • 禁用设置授权器--disable-settings-authorizer允许未经身份验证的用户访问设置页面。在生产环境中应禁用此功能。

Metrics Server

Metrics Server 使用每个节点上的kubelet公开的摘要 API 聚合集群使用数据。它使用kube-aggregatorkube-apiserver上注册。Metrics Server 通过 Metrics API 公开收集的指标,这些指标被水平 Pod 自动缩放器和垂直 Pod 自动缩放器使用。用于调试集群的kubectl top也使用 Metrics API。Metrics Server 特别设计用于自动缩放。

在某些 Kubernetes 发行版上,默认情况下启用了 Metrics Server。您可以使用以下命令在minikube上启用它:

$ minikube addons enable metrics-server

您可以使用以下命令检查 Metrics Server 是否已启用:

$ kubectl get apiservices | grep metrics
v1beta1.metrics.k8s.io                 kube-system/metrics-server   True        7m17s

启用 Metrics Server 后,需要一些时间来查询摘要 API 并关联数据。您可以使用kubectl top node来查看当前的指标:

$ kubectl top node
NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
minikube   156m         7%     1140Mi          30%
$ kubectl top pod
NAME         CPU(cores)   MEMORY(bytes)
nginx-good   0m           2Mi

与其他服务和组件类似,Metrics Server 也有配置参数。在生产集群中,请确保 Metrics Server 不使用--kubelet-insecure-tls标志,该标志允许 Metrics Server 跳过 CA 对证书的验证。

第三方监控工具

第三方监控工具集成到 Kubernetes 中,提供了更多功能和对 Kubernetes 资源健康的洞察。在本节中,我们将讨论 Prometheus 和 Grafana,它们是开源社区中最流行的监控工具。

Prometheus 和 Grafana

Prometheus 是由 SoundCloud 开发并被 CNCF 采用的开源仪表和数据收集框架。Prometheus 可以用来查看不同数据点的时间序列数据。Prometheus 使用拉取系统。它发送一个称为抓取的 HTTP 请求,从系统组件(包括 API 服务器、node-exporterkubelet)获取数据。抓取的响应和指标存储在 Prometheus 服务器上的自定义数据库中。

让我们看看如何设置 Prometheus 来监视 Kubernetes 中的一个命名空间:

  1. 创建一个命名空间:
$kubectl create namespace monitoring
  1. 定义一个集群角色来读取 Kubernetes 对象,如 pods、nodes 和 services,并将角色绑定到一个服务账户。在这个例子中,我们使用默认的服务账户:
$ cat prometheus-role.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: prometheus
rules:
- apiGroups: [""]
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]
$ kubectl create -f prometheus-role.yaml
clusterrole.rbac.authorization.k8s.io/prometheus created

现在,我们创建一个角色绑定,将角色与默认服务账户关联起来:

$ cat prometheus-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: default
  namespace: monitoring
  1. Prometheus 使用 ConfigMap 来指定抓取规则。以下规则抓取kube-apiserver。可以定义多个抓取来获取指标:
$ cat config_prometheus.yaml apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-server-conf
  labels:
    name: prometheus-server-conf
  namespace: monitoring
data:
  prometheus.yml: |-
    global:
      scrape_interval: 5s
      evaluation_interval: 5s
  scrape_configs:    - job_name: 'kubernetes-apiservers'
      kubernetes_sd_configs:
      - role: endpoints
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
        action: keep
        regex: default;kubernetes;https
  1. 为 Prometheus 创建一个部署:
spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.12.0
          args:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--storage.tsdb.path=/prometheus/"
          ports:
            - containerPort: 9090
          volumeMounts:
            - name: prometheus-config-volume
              mountPath: /etc/prometheus/
            - name: prometheus-storage-volume
              mountPath: /prometheus/
      volumes:
        - name: prometheus-config-volume
          configMap:
            defaultMode: 420
            name: prometheus-server-conf
        - name: prometheus-storage-volume
          emptyDir: {}
  1. 部署成功后,可以使用端口转发或 Kubernetes 服务来访问仪表板:
$ kubectl port-forward <prometheus-pod> 8080:9090 -n monitoring

这样可以为 Prometheus pod 启用端口转发。现在,您可以使用端口8080上的集群 IP 来访问它:

图 10.5 – Prometheus 仪表板

图 10.5 – Prometheus 仪表板

查询可以输入为表达式,并查看结果为图形控制台消息。使用 Prometheus 查询,集群管理员可以查看由 Prometheus 监视的集群、节点和服务的状态。

让我们看一些对集群管理员有帮助的 Prometheus 查询的例子:

  • Kubernetes CPU 使用率:
sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name!=""}[5m]))
  • Kubernetes 命名空间的 CPU 使用率:
sum(rate(container_cpu_usage_seconds_total{container_name!="POD",namespace!=""}[5m])) by (namespace)
  • 按 pod 的 CPU 请求:
sum(kube_pod_container_resource_requests_cpu_cores) by (pod)

让我们看一下演示集群的命名空间的 CPU 使用率:

图 10.6 – 命名空间的 CPU 使用率

图 10.6 – 命名空间的 CPU 使用率

Prometheus 还允许集群管理员使用 ConfigMaps 设置警报:

prometheus.rules: |-
    groups:
    - name: Demo Alert
      rules:
      - alert: High Pod Memory
        expr: sum(container_memory_usage_bytes{pod!=""})  by (pod) > 1000000000
        for: 1m
        labels:
          severity: high
        annotations:
          summary: High Memory Usage

当容器内存使用大于1000 MB 并持续1分钟时,此警报将触发一个带有high严重性标签的警报:

图 10.7 – Prometheus 警报

图 10.7 – Prometheus 警报

使用Alertmanager与 Prometheus 有助于对来自诸如 Prometheus 的应用程序的警报进行去重、分组和路由,并将其路由到集成客户端,包括电子邮件、OpsGenie 和 PagerDuty。

Prometheus 与其他增强数据可视化和警报管理的第三方工具很好地集成。Grafana 就是这样的工具。Grafana 允许对从 Prometheus 检索的数据进行可视化、查询和警报。

现在让我们看看如何使用 Prometheus 设置 Grafana:

  1. Grafana 需要一个数据源进行摄入;在本例中,它是 Prometheus。数据源可以使用 UI 添加,也可以使用 ConfigMap 指定:
$ cat grafana-data.yaml                                   apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources
  namespace: monitoring
data:
  prometheus.yaml: |-
    {
        "apiVersion": 1,
        "datasources": [
            {
               "access":"proxy",
                "editable": true,
                "name": "prometheus",
                "orgId": 1,
                "type": "prometheus",
                "url": "http://192.168.99.128:30000",
                "version": 1
            }
        ]
    }
  1. 为 Grafana 创建一个部署:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      name: grafana
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:latest
        ports:
        - name: grafana
          containerPort: 3000
        volumeMounts:
          - mountPath: /var/lib/grafana
            name: grafana-storage
          - mountPath: /etc/grafana/provisioning/datasources
            name: grafana-datasources
            readOnly: false
      volumes:
        - name: grafana-storage
          emptyDir: {}
        - name: grafana-datasources
          configMap:
              name: grafana-datasources
  1. 然后可以使用端口转发或 Kubernetes 服务来访问仪表板:
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: monitoring
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/port:   '3000'
spec:
  selector:
    app: grafana
  type: NodePort
  ports:
    - port: 3000
      targetPort: 3000
      nodePort: 32000
  1. 默认情况下,仪表板的用户名和密码为admin。登录后,您可以设置一个新的仪表板,或者从 Grafana 导入一个仪表板。要导入一个仪表板,您可以点击+ > 导入,然后会出现以下屏幕。在第一个文本框中输入315,以从 Grafana 导入仪表板 315:图 10.8 – 在 Grafana 中导入仪表板

图 10.8 – 在 Grafana 中导入仪表板

  1. 这个仪表板是由 Instrumentisto 团队创建的。导入时,下一个屏幕上的所有字段将自动填充:图 10.9 – Grafana 仪表板 – 315

图 10.9 – Grafana 仪表板 – 315

  1. 也可以使用自定义的 Prometheus 查询创建一个新的仪表板:图 10.10 – 自定义仪表板

图 10.10 – 自定义仪表板

  1. 与 Prometheus 类似,您可以在每个仪表板上设置警报:

图 10.11 – Grafana 中的新警报

图 10.11 – Grafana 中的新警报

还有其他与 Prometheus 集成的工具,使其成为 DevOps 和集群管理员的宝贵工具。

总结

在本章中,我们讨论了可用性作为 CIA 三要素的重要组成部分。我们从安全的角度讨论了资源管理和实时资源监控的重要性。然后,我们介绍了资源请求和限制,这是 Kubernetes 资源管理的核心概念。接下来,我们讨论了资源管理以及集群管理员如何积极确保 Kubernetes 对象不会表现不端。

我们深入研究了命名空间资源配额和限制范围的细节,并看了如何设置它的示例。然后我们转向资源监控。我们看了一些作为 Kubernetes 一部分提供的内置监视器,包括 Dashboard 和 Metrics Server。最后,我们看了一些第三方工具 - Prometheus 和 Grafana - 这些工具更强大,大多数集群管理员和 DevOps 工程师更喜欢使用。

通过资源管理,集群管理员可以确保 Kubernetes 集群中的服务有足够的资源可用于运行,并且恶意或行为不端的实体不会独占所有资源。另一方面,资源监控有助于实时识别问题和症状。与资源监控一起使用的警报管理可以在发生问题时通知利益相关者,例如磁盘空间不足或内存消耗过高,从而确保停机时间最小化。

在下一章中,我们将详细讨论深度防御。我们将看看集群管理员和 DevOps 工程师如何通过分层安全配置、资源管理和资源监控来增强安全性。深度防御将引入更多的工具包,以确保在生产环境中可以轻松检测和减轻攻击。

问题

  1. 资源请求和限制之间有什么区别?

  2. 定义一个将内存限制限制为 500 mi 的资源配额。

  3. 限制范围与资源配额有何不同?

  4. Kubernetes Dashboard 的推荐认证方法是什么?

  5. 哪个是最广泛推荐的资源监控工具?

更多参考资料

您可以参考以下链接,了解本章涵盖的主题的更多信息:

第十一章:深度防御

深度防御是一种在网络安全中应用多层安全控制来保护有价值资产的方法。在传统或单片式 IT 环境中,我们可以列举出许多:认证、加密、授权、日志记录、入侵检测、防病毒、虚拟私人网络VPN)、防火墙等等。您可能会发现这些安全控制也存在于 Kubernetes 集群中(而且应该存在)。

在之前的章节中,我们已经讨论了认证、授权、准入控制器、保护 Kubernetes 组件、保护配置、加固镜像和 Kubernetes 工作负载等主题。所有这些都构建了不同的安全控制层,以保护您的 Kubernetes 集群。在本章中,我们将讨论构建额外安全控制层的主题,这些主题与 Kubernetes 集群中的运行时防御最相关。以下是本章将要解决的问题:您的集群是否暴露了任何敏感数据?如果 Kubernetes 集群发生攻击,您能否检测到攻击?您的 Kubernetes 集群能够承受攻击吗?您如何应对攻击?

在本章中,我们将讨论 Kubernetes 审计,然后介绍高可用性的概念,并讨论如何在 Kubernetes 集群中应用高可用性。接下来,我们将介绍 Vault,这是一个方便的秘密管理产品,适用于 Kubernetes 集群。然后,我们将讨论如何使用 Falco 来检测 Kubernetes 集群中的异常活动。最后但同样重要的是,我们将介绍 Sysdig Inspect 和用户空间的检查点和资源(也称为CRIU)用于取证。

本章将涵盖以下主题:

  • 介绍 Kubernetes 审计

  • 在 Kubernetes 集群中启用高可用性

  • 使用 Vault 管理秘密

  • 使用 Falco 检测异常

  • 使用 Sysdig Inspect 和 CRIU 进行取证

介绍 Kubernetes 审计

Kubernetes 审计是在 1.11 版本中引入的。Kubernetes 审计记录事件,例如创建部署,修补 pod,删除命名空间等,按照时间顺序进行记录。通过审计,Kubernetes 集群管理员能够回答以下问题:

  • 发生了什么?(创建了一个 pod,是什么类型的 pod)

  • 谁做的?(来自用户/管理员)

  • 发生在什么时候?(事件的时间戳)

  • 它发生在哪里?(Pod 是在哪个命名空间中创建的?)

从安全的角度来看,审计使 DevOps 团队和安全团队能够通过跟踪 Kubernetes 集群内发生的事件来更好地检测和预防异常。

在 Kubernetes 集群中,是kube-apiserver进行审计。当请求(例如,创建一个命名空间)发送到kube-apiserver时,请求可能会经过多个阶段。每个阶段将生成一个事件。已知的阶段如下:

  • RequestReceived:在审计处理程序接收请求而不处理它时生成事件。

  • RequestStarted:在发送响应头并发送响应正文之间生成事件,仅适用于长时间运行的请求,如watch

  • RequestComplete:在发送响应正文时生成事件。

  • Panic:当发生紧急情况时生成事件。

在本节中,我们将首先介绍 Kubernetes 审计策略,然后向您展示如何启用 Kubernetes 审计以及持久化审计记录的几种方法。

Kubernetes 审计策略

由于记录 Kubernetes 集群内发生的一切事情并不现实,审计策略允许用户定义关于应记录何种事件以及应记录事件的多少细节的规则。当kube-apiserver处理事件时,它将按顺序比较审计策略中的规则列表。第一个匹配的规则还决定了事件的审计级别。让我们看看审计策略是什么样子。以下是一个示例:

apiVersion: audit.k8s.io/v1 # This is required.
kind: Policy
# Skip generating audit events for all requests in RequestReceived stage. This can be either set at the policy level or rule level.
omitStages:
  - "RequestReceived"
rules:
  # Log pod changes at RequestResponse level
  - level: RequestResponse
    verbs: ["create", "update"]
    namespace: ["ns1", "ns2", "ns3"]
    resources:
    - group: ""
# Only check access to resource "pods", not the sub-resource of pods which is consistent with the RBAC policy.
      resources: ["pods"]
# Log "pods/log", "pods/status" at Metadata level
  - level: Metadata
    resources:
    - group: ""
      resources: ["pods/log", "pods/status"]
# Don't log authenticated requests to certain non-resource URL paths.
  - level: None
    userGroups: ["system:authenticated"]
    nonResourceURLs: ["/api*", "/version"]
# Log configmap and secret changes in all other namespaces at the Metadata level.
  - level: Metadata
    resources:
    - group: "" # core API group
      resources: ["secrets", "configmaps"]

您可以在审计策略中配置多个审计规则。每个审计规则将由以下字段配置:

  • level:定义审计事件详细程度的审计级别。

  • resources:审计的 Kubernetes 对象。资源可以通过应用程序编程接口API)组和对象类型来指定。

  • nonResourcesURL:与审计的任何资源不相关的非资源统一资源定位符URL)路径。

  • namespace:决定哪个命名空间中的 Kubernetes 对象将接受审计。空字符串将用于选择非命名空间对象,空列表意味着每个命名空间。

  • verb:决定将接受审计的 Kubernetes 对象的具体操作,例如createupdatedelete

  • users:决定审计规则适用于的经过身份验证的用户

  • userGroups:决定认证用户组适用于的审计规则。

  • omitStages:跳过在给定阶段生成事件。这也可以在策略级别设置。

审计策略允许您通过指定verbnamespaceresources等在细粒度级别上配置策略。规则的审计级别定义了应记录事件的详细程度。有四个审计级别,如下所述:

  • None:不记录与审计规则匹配的事件。

  • Metadata:当事件匹配审计规则时,记录请求到kube-apiserver的元数据(如usertimestampresourceverb等)。

  • Request:当事件匹配审计规则时,记录元数据以及请求正文。这不适用于非资源 URL。

  • RequestResponse:当事件匹配审计规则时,记录元数据、请求和响应正文。这不适用于非资源请求。

请求级别的事件比元数据级别的事件更详细,而RequestResponse级别的事件比请求级别的事件更详细。高详细度需要更多的输入/输出(I/O)吞吐量和存储。了解审计级别之间的差异非常必要,这样您就可以正确定义审计规则,既可以节约资源又可以保障安全。成功配置审计策略后,让我们看看审计事件是什么样子的。以下是一个元数据级别的审计事件:

{
  "kind": "Event",
  "apiVersion": "audit.k8s.io/v1",
  "level": "Metadata",
  "auditID": "05698e93-6ad7-4f4e-8ae9-046694bee469",
  "stage": "ResponseComplete",
  "requestURI": "/api/v1/namespaces/ns1/pods",
  "verb": "create",
  "user": {
    "username": "admin",
    "uid": "admin",
    "groups": [
      "system:masters",
      "system:authenticated"
    ]
  },
  "sourceIPs": [
    "98.207.36.92"
  ],
  "userAgent": "kubectl/v1.17.4 (darwin/amd64) kubernetes/8d8aa39",
  "objectRef": {
    "resource": "pods",
    "namespace": "ns1",
    "name": "pod-1",
    "apiVersion": "v1"
  },
  "responseStatus": {
    "metadata": {},
    "code": 201
  },
  "requestReceivedTimestamp": "2020-04-09T07:10:52.471720Z",
  "stageTimestamp": "2020-04-09T07:10:52.485551Z",
  "annotations": {
    "authorization.k8s.io/decision": "allow",
    "authorization.k8s.io/reason": ""
  }
}

前面的审计事件显示了usertimestamp、被访问的对象、授权决定等。请求级别的审计事件在审计事件中的requestObject字段中提供了额外的信息。您将在requestObject字段中找到工作负载的规范,如下所示:

  "requestObject": {
    "kind": "Pod",
    "apiVersion": "v1",
    "metadata": {
      "name": "pod-2",
      "namespace": "ns2",
      "creationTimestamp": null,
      ...
    },
    "spec": {
      "containers": [
        {
          "name": "echo",
          "image": "busybox",
          "command": [
            "sh",
            "-c",
            "echo 'this is echo' && sleep 1h"
          ],
          ...
          "imagePullPolicy": "Always"
        }
      ],
      ...
      "securityContext": {},
    },

RequestResponse级别的审计事件是最详细的。事件中的responseObject实例几乎与requestObject相同,但包含了额外的信息,如资源版本和创建时间戳,如下面的代码块所示:

{
  "responseObject": {
      ...
      "selfLink": "/api/v1/namespaces/ns3/pods/pod-3",
      "uid": "3fd18de1-7a31-11ea-9e8d-0a39f00d8287",
      "resourceVersion": "217243",
      "creationTimestamp": "2020-04-09T07:10:53Z",
      "tolerations": [
        {
          "key": "node.kubernetes.io/not-ready",
          "operator": "Exists",
          "effect": "NoExecute",
          "tolerationSeconds": 300
        },
        {
          "key": "node.kubernetes.io/unreachable",
          "operator": "Exists",
          "effect": "NoExecute",
          "tolerationSeconds": 300
        }
      ],
      ...
    },
 }

请务必正确选择审计级别。更详细的日志提供了对正在进行的活动更深入的洞察。然而,存储和处理审计事件的时间成本更高。值得一提的是,如果在 Kubernetes 秘密对象上设置了请求或RequestResponse审计级别,秘密内容将被记录在审计事件中。如果将审计级别设置为比包含敏感数据的 Kubernetes 对象的元数据更详细,您应该使用敏感数据遮蔽机制,以避免秘密被记录在审计事件中。

Kubernetes 审计功能通过对象类型、命名空间、操作、用户等提供了对 Kubernetes 对象的审计灵活性。由于 Kubernetes 审计默认情况下未启用,接下来,让我们看看如何启用 Kubernetes 审计并存储审计记录。

配置审计后端

为了启用 Kubernetes 审计,您需要在启动kube-apiserver时传递--audit-policy-file标志和您的审计策略文件。可以配置两种类型的审计后端来处理审计事件:日志后端和 webhook 后端。让我们来看看它们。

日志后端

日志后端将审计事件写入主节点上的文件。以下标志用于在kube-apiserver中配置日志后端:

  • --log-audit-path:指定主节点上的日志路径。这是打开或关闭日志后端的标志。

  • --audit-log-maxage:指定保留审计记录的最大天数。

  • --audit-log-maxbackup:指定主节点上要保留的审计文件的最大数量。

  • --audit-log-maxsize:指定在日志文件被轮换之前的最大兆字节大小。

让我们来看看 webhook 后端。

webhook 后端

webhook 后端将审计事件写入注册到kube-apiserver的远程 webhook。要启用 webhook 后端,您需要使用 webhook 配置文件设置--audit-webhook-config-file标志。此标志也在启动kube-apiserver时指定。以下是一个用于为稍后将更详细介绍的 Falco 服务注册 webhook 后端的 webhook 配置的示例:

apiVersion: v1
kind: Config
clusters:
- name: falco
  cluster:
    server: http://$FALCO_SERVICE_CLUSTERIP:8765/k8s_audit
contexts:
- context:
    cluster: falco
    user: ""
  name: default-context
current-context: default-context
preferences: {}
users: []

server字段中指定的 URL(http://$FALCO_SERVICE_CLUSTERIP:8765/k8s_audit)是审计事件将要发送到的远程端点。自 Kubernetes 1.13 版本以来,可以通过AuditSink对象动态配置 webhook 后端,该对象仍处于 alpha 阶段。

在本节中,我们介绍了 Kubernetes 审计,介绍了审计策略和审计后端。在下一节中,我们将讨论 Kubernetes 集群中的高可用性。

在 Kubernetes 集群中启用高可用性

可用性指的是用户访问服务或系统的能力。系统的高可用性确保了系统的约定的正常运行时间。例如,如果只有一个实例来提供服务,而该实例宕机,用户将无法再访问该服务。具有高可用性的服务由多个实例提供。当一个实例宕机时,备用实例仍然可以提供服务。以下图表描述了具有和不具有高可用性的服务:

图 11.1 - 具有和不具有高可用性的服务

图 11.1 - 具有和不具有高可用性的服务

在 Kubernetes 集群中,通常会有多个工作节点。集群的高可用性得到了保证,即使一个工作节点宕机,仍然有其他工作节点来承载工作负载。然而,高可用性不仅仅是在集群中运行多个节点。在本节中,我们将从三个层面来看 Kubernetes 集群中的高可用性:工作负载、Kubernetes 组件和云基础设施。

启用 Kubernetes 工作负载的高可用性

对于 Kubernetes 工作负载,比如部署和 StatefulSet,您可以在规范中指定replicas字段,用于指定微服务运行多少个复制的 pod,并且控制器将确保在集群中的不同工作节点上有x个 pod 运行,如replicas字段中指定的那样。DaemonSet 是一种特殊的工作负载;控制器将确保在集群中的每个节点上都有一个 pod 运行,假设您的 Kubernetes 集群有多个节点。因此,在部署或 StatefulSet 中指定多个副本,或者使用 DaemonSet,将确保您的工作负载具有高可用性。为了确保工作负载的高可用性,还需要确保 Kubernetes 组件的高可用性。

启用 Kubernetes 组件的高可用性

高可用性也适用于 Kubernetes 组件。让我们来回顾一下几个关键的 Kubernetes 组件,如下所示:

  • kube-apiserver:Kubernetes API 服务器(kube-apiserver)是一个控制平面组件,用于验证和配置诸如 pod、服务和控制器之类的对象的数据。它使用REepresentational State TransferREST)请求与对象进行交互。

  • etcdetcd是一个高可用性的键值存储,用于存储配置、状态和元数据等数据。其watch功能使 Kubernetes 能够监听配置的更新并相应地进行更改。

  • kube-schedulerkube-scheduler是 Kubernetes 的默认调度程序。它会观察新创建的 pod 并将 pod 分配给节点。

  • kube-controller-manager:Kubernetes 控制器管理器是观察状态更新并相应地对集群进行更改的核心控制器的组合。

如果kube-apiserver宕机,那么基本上您的集群也会宕机,因为用户或其他 Kubernetes 组件依赖于与kube-apiserver通信来执行其任务。如果etcd宕机,那么集群和对象的状态将无法被消费。kube-schedulerkube-controller-manager也很重要,以确保工作负载在集群中正常运行。所有这些组件都在主节点上运行,以确保组件的高可用性。一个简单的方法是为您的 Kubernetes 集群启动多个主节点,可以通过kopskubeadm来实现。您会发现类似以下的内容:

$ kubectl get pods -n kube-system
...
etcd-manager-events-ip-172-20-109-109.ec2.internal       1/1     Running   0          4h15m
etcd-manager-events-ip-172-20-43-65.ec2.internal         1/1     Running   0          4h16m
etcd-manager-events-ip-172-20-67-151.ec2.internal        1/1     Running   0          4h16m
etcd-manager-main-ip-172-20-109-109.ec2.internal         1/1     Running   0          4h15m
etcd-manager-main-ip-172-20-43-65.ec2.internal           1/1     Running   0          4h15m
etcd-manager-main-ip-172-20-67-151.ec2.internal          1/1     Running   0          4h16m
kube-apiserver-ip-172-20-109-109.ec2.internal            1/1     Running   3          4h15m
kube-apiserver-ip-172-20-43-65.ec2.internal              1/1     Running   4          4h16m
kube-apiserver-ip-172-20-67-151.ec2.internal             1/1     Running   4          4h15m
kube-controller-manager-ip-172-20-109-109.ec2.internal   1/1     Running   0          4h15m
kube-controller-manager-ip-172-20-43-65.ec2.internal     1/1     Running   0          4h16m
kube-controller-manager-ip-172-20-67-151.ec2.internal    1/1     Running   0          4h15m
kube-scheduler-ip-172-20-109-109.ec2.internal            1/1     Running   0          4h15m
kube-scheduler-ip-172-20-43-65.ec2.internal              1/1     Running   0          4h15m
kube-scheduler-ip-172-20-67-151.ec2.internal             1/1     Running   0          4h16m

现在您有多个kube-apiserver pod、etcd pod、kube-controller-manager pod 和kube-scheduler pod 在kube-system命名空间中运行,并且它们在不同的主节点上运行。还有一些其他组件,如kubeletkube-proxy,它们在每个节点上运行,因此它们的可用性由节点的可用性保证,并且kube-dns默认情况下会启动多个 pod,因此它们的高可用性是得到保证的。无论您的 Kubernetes 集群是在公共云上运行还是在私有数据中心中运行——基础设施都是支持 Kubernetes 集群可用性的支柱。接下来,我们将讨论云基础设施的高可用性,并以云提供商为例。

启用云基础设施的高可用性

云提供商通过位于不同地区的多个数据中心提供全球范围的云服务。云用户可以选择在哪个地区和区域(实际数据中心)托管他们的服务。区域和区域提供了对大多数类型的物理基础设施和基础设施软件服务故障的隔离。请注意,云基础设施的可用性也会影响托管在云中的 Kubernetes 集群上运行的服务。您应该利用云的高可用性,并最终确保在 Kubernetes 集群上运行的服务的高可用性。以下代码块提供了使用kops指定区域的示例,以利用云基础设施的高可用性:

export NODE_SIZE=${NODE_SIZE:-t2.large}
export MASTER_SIZE=${MASTER_SIZE:-t2.medium}
export ZONES=${ZONES:-"us-east-1a,us-east-1b,us-east-1c"}
export KOPS_STATE_STORE="s3://my-k8s-state-store2/"
kops create cluster k8s-clusters.k8s-demo-zone.com \
  --cloud aws \
  --node-count 3 \
  --zones $ZONES \
  --node-size $NODE_SIZE \
  --master-size $MASTER_SIZE \
  --master-zones $ZONES \
  --networking calico \
  --kubernetes-version 1.14.3 \
  --yes \

Kubernetes 集群的节点如下所示:

$ kops validate cluster
...
INSTANCE GROUPS
NAME			ROLE	MACHINETYPE	MIN	MAX	SUBNETS
master-us-east-1a	Master	t2.medium	1	1	us-east-1a
master-us-east-1b	Master	t2.medium	1	1	us-east-1b
master-us-east-1c	Master	t2.medium	1	1	us-east-1c
nodes			Node	t2.large	3	3	us-east-1a,us-east-1b,us-east-1c

前面的代码块显示了分别在us-east-1aus-east-1bus-east-1c可用区运行的三个主节点。因此,作为工作节点,即使其中一个数据中心宕机或正在维护,主节点和工作节点仍然可以在其他数据中心中运行。

在本节中,我们已经讨论了 Kubernetes 工作负载、Kubernetes 组件和云基础设施的高可用性。让我们使用以下图表来总结 Kubernetes 集群的高可用性:

图 11.2-云中 Kubernetes 集群的高可用性

图 11.2-云中 Kubernetes 集群的高可用性

现在,让我们转到下一个关于在 Kubernetes 集群中管理秘密的主题。

使用 Vault 管理秘密

秘密管理是一个重要的话题,许多开源和专有解决方案已经被开发出来,以帮助解决不同平台上的秘密管理问题。因此,在 Kubernetes 中,它的内置Secret对象用于存储秘密数据,并且实际数据与其他 Kubernetes 对象一起存储在etcd中。默认情况下,秘密数据以明文(编码格式)存储在etcd中。etcd可以配置为在静止状态下加密秘密。同样,如果etcd未配置为使用传输层安全性TLS)加密通信,则秘密数据也以明文传输。除非安全要求非常低,否则建议在 Kubernetes 集群中使用第三方解决方案来管理秘密。

在本节中,我们将介绍 Vault,这是一个Cloud Native Computing FoundationCNCF)秘密管理项目。Vault 支持安全存储秘密、动态秘密生成、数据加密、密钥吊销等。在本节中,我们将重点介绍如何在 Kubernetes 集群中为应用程序存储和提供秘密。现在,让我们看看如何为 Kubernetes 集群设置 Vault。

设置 Vault

您可以使用helm在 Kubernetes 集群中部署 Vault,如下所示:

helm install vault --set='server.dev.enabled=true' https://github.com/hashicorp/vault-helm/archive/v0.4.0.tar.gz

请注意,设置了server.dev.enabled=true。这对开发环境很好,但不建议在生产环境中设置。您应该看到有两个正在运行的 pod,如下所示:

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
vault-0                                 1/1     Running   0          80s
vault-agent-injector-7fd6b9588b-fgsnj   1/1     Running   0          80s

vault-0 pod 是用于管理和存储秘密的 pod,而vault-agent-injector-7fd6b9588b-fgsnj pod 负责将秘密注入带有特殊 vault 注释的 pod 中,我们将在提供和轮换秘密部分中更详细地展示。接下来,让我们为postgres数据库连接创建一个示例秘密,如下所示:

vault kv put secret/postgres username=alice password=pass

请注意,前面的命令需要在vault-0 pod 内执行。由于您希望限制 Kubernetes 集群中仅有相关应用程序可以访问秘钥,您可能希望定义一个策略来实现,如下所示:

cat <<EOF > /home/vault/app-policy.hcl
path "secret*" {
  capabilities = ["read"]
}
EOF
vault policy write app /home/vault/app-policy.hcl

现在,您有一个定义了在secret路径下读取秘密权限的策略,比如secret/postgres。接下来,您希望将策略与允许的实体关联,比如 Kubernetes 中的服务账户。这可以通过执行以下命令来完成:

vault auth enable kubernetes
vault write auth/kubernetes/config \
   token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
   kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
   kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
vault write auth/kubernetes/role/myapp \
   bound_service_account_names=app \
   bound_service_account_namespaces=demo \
   policies=app \
   ttl=24h

Vault 可以利用 Kubernetes 的天真认证,然后将秘密访问策略绑定到服务账户。现在,命名空间 demo 中的服务账户 app 可以访问postgres秘密。现在,让我们在vault-app.yaml文件中部署一个演示应用程序,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  labels:
    app: vault-agent-demo
spec:
  selector:
    matchLabels:
      app: vault-agent-demo
  replicas: 1
  template:
    metadata:
      annotations:
      labels:
        app: vault-agent-demo
    spec:
      serviceAccountName: app
      containers:
      - name: app
        image: jweissig/app:0.0.1
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app
  labels:
    app: vault-agent-demo

请注意,在上述的.yaml文件中,尚未添加注释,因此在创建应用程序时,秘密不会被注入,也不会添加 sidecar 容器。代码可以在以下片段中看到:

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
app-668b8bcdb9-js9mm                    1/1     Running   0          3m23s

接下来,我们将展示秘密注入的工作原理。

提供和轮换秘密

我们在部署应用程序时不展示秘密注入的原因是,我们想向您展示在注入到演示应用程序 pod 之前和之后的详细差异。现在,让我们使用以下 Vault 注释来补丁部署:

$ cat patch-template-annotation.yaml
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-inject-status: "update"
        vault.hashicorp.com/agent-inject-secret-postgres: "secret/postgres"
        vault.hashicorp.com/agent-inject-template-postgres: |
          {{- with secret "secret/postgres" -}}
          postgresql://{{ .Data.data.username }}:{{ .Data.data.password }}@postgres:5432/wizard
          {{- end }}
        vault.hashicorp.com/role: "myapp"

上述注释规定了将注入哪个秘密,以及以什么格式和使用哪个角色。一旦我们更新了演示应用程序的部署,我们将发现秘密已经被注入,如下所示:

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
app-68d47bb844-2hlrb                    2/2     Running   0          13s
$ kubectl -n demo exec -it app-68d47bb844-2hlrb -c app -- cat /vault/secrets/postgres
postgresql://alice:pass@postgres:5432/wizard

让我们来看一下 pod 的规范(而不是补丁后的部署)-与补丁后的部署规范相比,您会发现以下内容(用粗体标记)已经添加:

  containers:
  - image: jweissig/app:0.0.1
    ...
    volumeMounts:
    - mountPath: /vault/secrets
      name: vault-secrets
  - args:
    - echo ${VAULT_CONFIG?} | base64 -d > /tmp/config.json && vault agent -config=/tmp/config.json
    command:
    - /bin/sh
    - -ec
    image: vault:1.3.2
    name: vault-agent
    volumeMounts:
    - mountPath: /vault/secrets
      name: vault-secrets
 initContainers:
  - args:
    - echo ${VAULT_CONFIG?} | base64 -d > /tmp/config.json && vault agent -config=/tmp/config.json
    command:
    - /bin/sh
    - -ec
    image: vault:1.3.2
    name: vault-agent-init
    volumeMounts:
    - mountPath: /vault/secrets
      name: vault-secrets
  volumes:
   - emptyDir:
      medium: Memory
    name: vault-secrets

在上述列出的变化中值得一提的几件事情:注入了一个名为vault-agent-initinit容器和一个名为vault-agent的 sidecar 容器,以及一个名为vault-secretsemptyDir类型卷。这就是为什么在补丁之后,你会看到演示应用程序 pod 中运行了两个容器。此外,vault-secrets卷被挂载在init容器、sidecar容器和app容器的/vault/secrets/目录中。秘密存储在vault-secrets卷中。通过预定义的变异 webhook 配置(通过helm安装)来完成 pod 规范的修改,如下所示:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  ...
  name: vault-agent-injector-cfg
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    caBundle: <CA_BUNDLE>
    service:
      name: vault-agent-injector-svc
      namespace: demo
      path: /mutate
  failurePolicy: Ignore
  name: vault.hashicorp.com
  namespaceSelector: {}
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    resources:
    - pods
    scope: '*'

注册到kube-apiserver的变异 webhook 配置基本上告诉kube-apiserver将任何 pod 的创建或更新请求重定向到demo命名空间中的vault-agent-injector-svc服务。服务的后面是vault-agent-injector pod。然后,vault-agent-injector pod 将查找相关的注释,并根据请求将init容器和sidecar容器以及存储秘密的卷注入到 pod 的规范中。为什么我们需要一个init容器和一个sidecar容器?init容器是为了预先填充我们的秘密,而sidecar容器是为了在整个应用程序生命周期中保持秘密数据同步。

现在,让我们运行以下代码来更新秘密,并看看会发生什么:

vault kv put secret/postgres username=alice password=changeme

现在,密码已从pass更新为changemevault pod 中。并且,在demo应用程序方面,我们可以看到在等待几秒钟后,它也已经更新了:

$ kubectl -n demo exec -it app-68d47bb844-2hlrb -c app -- cat /vault/secrets/postgres
postgresql://alice:changeme@postgres:5432/wizard

Vault 是一个强大的秘密管理解决方案,它的许多功能无法在单个部分中涵盖。我鼓励你阅读文档并尝试使用它来更好地了解 Vault。接下来,让我们谈谈在 Kubernetes 中使用 Falco 进行运行时威胁检测。

使用 Falco 检测异常

Falco 是一个 CNCF 开源项目,用于检测云原生环境中的异常行为或运行时威胁,比如 Kubernetes 集群。它是一个基于规则的运行时检测引擎,具有约 100 个现成的检测规则。在本节中,我们将首先概述 Falco,然后向您展示如何编写 Falco 规则,以便您可以构建自己的 Falco 规则来保护您的 Kubernetes 集群。

Falco 概述

Falco 被广泛用于检测云原生环境中的异常行为,特别是在 Kubernetes 集群中。那么,什么是异常检测?基本上,它使用行为信号来检测安全异常,比如泄露的凭据或异常活动,行为信号可以从你对实体的了解中得出正常行为是什么。

面临的挑战

要确定 Kubernetes 集群中的正常行为并不容易。从运行应用程序的角度来看,我们可以将它们分为三类,如下所示:

  • Kubernetes 组件kube-apiserverkube-proxykubelet容器运行时接口CRI)插件、容器网络接口CNI)插件等

  • 自托管应用程序:Java、Node.js、Golang、Python 等

  • 供应商服务:Cassandra、Redis、MySQL、NGINX、Tomcat 等

或者,从系统的角度来看,我们有以下类型的活动:

  • 文件活动,如打开、读取和写入

  • 进程活动,如execveclone系统调用

  • 网络活动,如接受、连接和发送

或者,从 Kubernetes 对象的角度来看:podsecretdeploymentnamespaceserviceaccountconfigmap

为了覆盖 Kubernetes 集群中发生的所有这些活动或行为,我们将需要丰富的信息来源。接下来,让我们谈谈 Falco 依赖的事件来源,以进行异常检测,以及这些来源如何涵盖前述的活动和行为。

异常检测的事件来源

Falco 依赖两个事件来源进行异常检测。一个是系统调用,另一个是 Kubernetes 审计事件。对于系统调用事件,Falco 使用内核模块来监听机器上的系统调用流,并将这些系统调用传递到用户空间(最近也支持了ebpf)。在用户空间,Falco 还会丰富原始系统调用事件的上下文,如进程名称、容器 ID、容器名称、镜像名称等。对于 Kubernetes 审计事件,用户需要启用 Kubernetes 审计策略,并将 Kubernetes 审计 webhook 后端注册到 Falco 服务端点。然后,Falco 引擎检查引擎中加载的任何 Falco 规则匹配的任何系统调用事件或 Kubernetes 审计事件。

讨论使用系统调用和 Kubernetes 审计事件作为事件源进行异常检测的原因也很重要。系统调用是应用程序与操作系统交互以访问文件、设备、网络等资源的编程方式。考虑到容器是一组具有自己专用命名空间的进程,并且它们共享节点上相同的操作系统,系统调用是可以用来监视容器活动的统一事件源。应用程序使用什么编程语言并不重要;最终,所有函数都将被转换为系统调用以与操作系统交互。看一下下面的图表:

图 11.3 - 容器和系统调用

图 11.3 - 容器和系统调用

在上图中,有四个运行不同应用程序的容器。这些应用程序可能使用不同的编程语言编写,并且它们都调用一个函数来以不同的函数名打开文件(例如,fopenopenos.Open)。然而,从操作系统的角度来看,所有这些应用程序都调用相同的系统调用open,但可能使用不同的参数。Falco 能够从系统调用中检索事件,因此无论应用程序是什么类型或使用什么编程语言都不重要。

另一方面,借助 Kubernetes 审计事件,Falco 可以完全了解 Kubernetes 对象的生命周期。这对于异常检测也很重要。例如,在生产环境中,以特权方式启动一个带有busybox镜像的 pod 可能是异常的。

总的来说,两个事件源——系统调用和 Kubernetes 审计事件——足以覆盖 Kubernetes 集群中发生的所有重要活动。现在,通过对 Falco 事件源的理解,让我们用一个高级架构图总结一下 Falco 的概述。

高级架构

Falco 主要由几个组件组成,如下:

  • Falco 规则:定义用于检测事件是否异常的规则。

  • Falco 引擎:使用 Falco 规则评估传入事件,并在事件匹配任何规则时产生输出。

  • 内核模块/Sysdig 库:在发送到 Falco 引擎进行评估之前,标记系统调用事件并丰富它们。

  • Web 服务器:监听 Kubernetes 审计事件并传递给 Falco 引擎进行评估。

以下图表显示了 Falco 的内部架构:

图 11.4 - Falco 的内部架构

](image/B15566_11_004.jpg)

图 11.4 - Falco 的内部架构

现在,我们已经总结了 Falco 的概述。接下来,让我们尝试创建一些 Falco 规则并检测任何异常行为。

创建 Falco 规则以检测异常

在我们深入研究 Falco 规则之前,请确保已通过以下命令安装了 Falco:

helm install --name falco stable/falco

Falco DaemonSet 应该在您的 Kubernetes 集群中运行,如下面的代码块所示:

$ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
falco-9h8tg   1/1     Running   10         62m
falco-cnt47   1/1     Running   5          3m45s
falco-mz6jg   1/1     Running   0          6s
falco-t4cpw   1/1     Running   0          10s

要启用 Kubernetes 审计并将 Falco 注册为 webhook 后端,请按照 Falco 存储库中的说明进行操作(github.com/falcosecurity/evolution/tree/master/examples/k8s_audit_config)。

Falco 规则中有三种类型的元素,如下所示:

  • 规则:触发警报的条件。规则具有以下属性:规则名称、描述、条件、优先级、来源、标签和输出。当事件匹配任何规则的条件时,根据规则的输出定义生成警报。

  • 宏:可以被其他规则或宏重复使用的规则条件片段。

  • 列表:可以被宏和规则使用的项目集合。

为了方便 Falco 用户构建自己的规则,Falco 提供了一些默认列表和宏。

创建系统调用规则

Falco 系统调用规则评估系统调用事件 - 更准确地说是增强的系统调用。系统调用事件字段由内核模块提供,并且与 Sysdig(Sysdig 公司构建的开源工具)过滤字段相同。策略引擎使用 Sysdig 的过滤器从系统调用事件中提取信息,如进程名称、容器映像和文件路径,并使用 Falco 规则进行评估。

以下是可以用于构建 Falco 规则的最常见的 Sysdig 过滤字段:

  • proc.name:进程名称

  • fd.name:写入或读取的文件名

  • container.id:容器 ID

  • container.image.repository:不带标签的容器映像名称

  • fd.sip 和 fd.sport:服务器Internet ProtocolIP)地址和服务器端口

  • fd.cip 和 fd.cport:客户端 IP 和客户端端口

  • evt.type: 系统调用事件(openconnectacceptexecve等)

让我们尝试构建一个简单的 Falco 规则。假设您有一个nginx pod,仅从/usr/share/nginx/html/目录提供静态文件。因此,您可以创建一个 Falco 规则来检测任何异常的文件读取活动,如下所示:

    - rule: Anomalous read in nginx pod
      desc: Detect any anomalous file read activities in Nginx pod.
      condition: (open_read and container and container.image.repository="kaizheh/insecure-nginx" and fd.directory != "/usr/share/nginx/html")
      output: Anomalous file read activity in Nginx pod (user=%user.name process=%proc.name file=%fd.name container_id=%container.id image=%container.image.repository)
      priority: WARNING

前面的规则使用了两个默认宏:open_readcontaineropen_read宏检查系统调用事件是否仅以读模式打开,而container宏检查系统调用事件是否发生在容器内。然后,该规则仅适用于运行kaizheh/insecure-nginx镜像的容器,并且fd.directory过滤器从系统调用事件中检索文件目录信息。在此规则中,它检查是否有任何文件读取超出/usr/share/nginx/html/目录。那么,如果nginx的配置错误导致文件路径遍历(在任意目录下读取文件)会怎么样?以下代码块显示了一个示例:

# curl insecure-nginx.insecure-nginx.svc.cluster.local/files../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/bin/false

与此同时,Falco 检测到超出指定目录的文件访问,输出如下:

08:22:19.484698397: Warning Anomalous file read activity in Nginx pod (user=<NA> process=nginx file=/etc/passwd container_id=439e2e739868 image=kaizheh/insecure-nginx) k8s.ns=insecure-nginx k8s.pod=insecure-nginx-7c99fdf44b-gffp4 container=439e2e739868 k8s.ns=insecure-nginx k8s.pod=insecure-nginx-7c99fdf44b-gffp4 container=439e2e739868

接下来,让我们看看如何使用 K8s 审计规则。

创建 K8s 审计规则

K8s 审计规则评估 Kubernetes 审计事件。在本章的前面部分,我们已经展示了 Kubernetes 审计事件记录的样子。与 Sysdig 过滤器类似,有两种方法可以从 Kubernetes 审计事件中检索信息。一种是使用JavaScript 对象表示法JSON)指针;另一种是使用 Falco 内置过滤器。以下是用于检索 Kubernetes 审计事件信息的一些常用 Falco 内置过滤器:

  • ka.verb: Kubernetes 审计事件的动词字段。jevt.value[/verb]是其对应的 JSON 指针。

  • ka.target.resource: Kubernetes 审计事件的资源字段。jevt.value[/objectRef/resource]是其对应的 JSON 指针。

  • ka.user.name: Kubernetes 审计事件的用户名字段。jevt.value[/user/username]是其对应的 JSON 指针。

  • ka.uri: Kubernetes 审计事件的requestURI字段。jet.value[/requestURI]是其对应的 JSON 指针。

让我们尝试构建一个简单的 K8s 审计规则。假设您不希望在kube-system命名空间中部署除了一些受信任的服务镜像(如kube-apiserveretcd-manager等)之外的镜像。因此,您可以创建一个 Falco 规则,如下所示:

- list: trusted_images
  items: [calico/node, kopeio/etcd-manager, k8s.gcr.io/kube-apiserver, k8s.gcr.io/kube-controller-manager, k8s.gcr.io/kube-proxy, k8s.gcr.io/kube-scheduler]
- rule: Untrusted Image Deployed in kube-system Namespace
  desc: >
    Detect an untrusted image deployed in kube-system namespace
  condition: >
    kevt and pod
    and kcreate
    and ka.target.namespace=kube-system
    and not ka.req.pod.containers.image.repository in (trusted_images)
  output: Untrusted image deployed in kube-system namespace (user=%ka.user.name image=%ka.req.pod.containers.image.repository resource=%ka.target.name)
  priority: WARNING
  source: k8s_audit
  tags: [k8s]

首先,我们定义了一个受信任的镜像列表,这些镜像将被允许部署到kube-system命名空间中。在规则中,我们使用了两个默认宏:podkcreatepod宏检查目标资源是否为 Pod,而kcreate检查动词是否为create。我们还检查目标命名空间是否为kube-system,并且部署的镜像不在trusted_images列表中。规则的source字段中的k8s_audit值表示此规则评估 Kubernetes 审计事件。然后,如果我们尝试在kube-system命名空间中部署busybox镜像的 Pod,我们将从 Falco 看到以下警报:

21:47:15.063915008: Warning Untrusted image deployed in kube-system namespace (user=admin image=busybox resource=pod-1)

请注意,为了使此规则起作用,需要将 Pod 创建的审计级别至少设置为“请求”级别,其中审计事件包括 Pod 的规范信息,例如镜像。

在本节中,我们介绍了 Falco,并向您展示了如何从系统调用和 Kubernetes 审计事件两个事件源创建 Falco 规则。这两个规则都用于基于工作负载或集群已知良性活动来检测异常活动。接下来,让我们谈谈如何在 Kubernetes 集群中进行取证工作。

使用 Sysdig Inspect 和 CRIU 进行取证。

在网络安全中,取证意味着收集、处理和分析信息,以支持漏洞缓解和/或欺诈、反情报或执法调查。您可以保存的数据越多,对收集的数据进行的分析越快,您就越能追踪攻击并更好地应对事件。在本节中,我们将向您展示如何使用 CRIU 和 Sysdig 开源工具来收集数据,然后介绍 Sysdig Inspect,这是一个用于分析 Sysdig 收集的数据的开源工具。

使用 CRIU 收集数据

CRIUCheckpoint and Restore In Userspace的缩写。它是一个可以冻结运行中的容器并在磁盘上捕获容器状态的工具。稍后,可以将磁盘上保存的容器和应用程序数据恢复到冻结时的状态。它对于容器快照、迁移和远程调试非常有用。从安全的角度来看,它特别有用于捕获容器中正在进行的恶意活动(以便您可以在检查点后立即终止容器),然后在沙盒环境中恢复状态以进行进一步分析。

CRIU 作为 Docker 插件工作,仍处于实验阶段,已知问题是 CRIU 在最近的几个版本中无法正常工作(github.com/moby/moby/issues/37344)。出于演示目的,我使用了较旧的 Docker 版本(Docker CE 17.03),并将展示如何使用 CRIU 对运行中的容器进行检查点,并将状态恢复为新容器。

要启用 CRIU,您需要在 Docker 守护程序中启用experimental模式,如下所示:

echo "{\"experimental\":true}" >> /etc/docker/daemon.json

然后,在重新启动 Docker 守护程序后,您应该能够成功执行docker checkpoint命令,就像这样:

# docker checkpoint
Usage:	docker checkpoint COMMAND
Manage checkpoints
Options:
      --help   Print usage
Commands:
  create      Create a checkpoint from a running container
  ls          List checkpoints for a container
  rm          Remove a checkpoint

然后,按照说明安装 CRIU(criu.org/Installation)。接下来,让我们看一个简单的示例,展示 CRIU 的强大之处。我有一个简单的busybox容器在运行,每秒增加1,如下面的代码片段所示:

# docker run -d --name looper --security-opt seccomp:unconfined busybox /bin/sh -c 'i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done'
91d68fafec8fcf11e7699539dec0b037220b1fcc856fb7050c58ab90ae8cbd13

睡了几秒钟后,我看到计数器的输出在增加,如下所示:

# sleep 5
# docker logs looper
0
1
2
3
4
5

接下来,我想对容器进行检查点,并将状态存储到本地文件系统,就像这样:

# docker checkpoint create --checkpoint-dir=/tmp looper checkpoint
checkpoint

现在,checkpoint状态已保存在/tmp目录下。请注意,除非在创建检查点时指定了--leave-running标志,否则容器 looper 将在检查点后被杀死。

然后,创建一个镜像容器,但不运行它,就像这样:

# docker create --name looper-clone --security-opt seccomp:unconfined busybox /bin/sh -c 'i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done'
49b9ade200e7da6bbb07057da02570347ad6fefbfc1499652ed286b874b59f2b

现在,我们可以启动具有存储状态的新looper-clone容器。让我们再等几秒钟,看看会发生什么。结果可以在下面的代码片段中看到:

# docker start --checkpoint-dir=/tmp --checkpoint=checkpoint looper-clone
# sleep 5
# docker logs looper-clone
6
7
8
9
10

新的looper-clone容器从6开始计数,这意味着状态(计数器为5)已成功恢复并使用。

CRIU 对容器取证非常有用,特别是当容器中发生可疑活动时。您可以对容器进行检查点(假设在集群中有多个副本运行),让 CRIU 杀死可疑容器,然后在沙盒环境中恢复容器的可疑状态以进行进一步分析。接下来,让我们谈谈另一种获取取证数据的方法。

使用 Sysdig 和 Sysdig Inspect

Sysdig 是一个用于 Linux 系统探索和故障排除的开源工具,支持容器。Sysdig 还可以用于通过在 Linux 内核中进行仪器化和捕获系统调用和其他操作系统事件来创建系统活动的跟踪文件。捕获功能使其成为容器化环境中的一种出色的取证工具。为了支持在 Kubernetes 集群中捕获系统调用,Sysdig 提供了一个kubectl插件,kubectl-capture,它使您可以像使用其他kubectl命令一样简单地捕获目标 pod 的系统调用。捕获完成后,可以使用强大的开源工具 Sysdig Inspect 进行故障排除和安全调查。

让我们继续以insecure-nginx为例,因为我们收到了 Falco 警报,如下面的代码片段所示:

08:22:19.484698397: Warning Anomalous file read activity in Nginx pod (user=<NA> process=nginx file=/etc/passwd container_id=439e2e739868 image=kaizheh/insecure-nginx) k8s.ns=insecure-nginx k8s.pod=insecure-nginx-7c99fdf44b-gffp4 container=439e2e739868 k8s.ns=insecure-nginx k8s.pod=insecure-nginx-7c99fdf44b-gffp4 container=439e2e739868

在触发警报时,nginx pod 仍然可能正在遭受攻击。您可以采取一些措施来应对。启动捕获,然后分析 Falco 警报的更多上下文是其中之一。

要触发捕获,请从github.com/sysdiglabs/kubectl-capture下载kubectl-capture并将其放置在其他kubectl插件中,就像这样:

$ kubectl plugin list
The following compatible plugins are available:
/Users/kaizhehuang/.krew/bin/kubectl-advise_psp
/Users/kaizhehuang/.krew/bin/kubectl-capture
/Users/kaizhehuang/.krew/bin/kubectl-ctx
/Users/kaizhehuang/.krew/bin/kubectl-krew
/Users/kaizhehuang/.krew/bin/kubectl-ns
/Users/kaizhehuang/.krew/bin/kubectl-sniff

然后,像这样在nginx pod 上启动捕获:

$ kubectl capture insecure-nginx-7c99fdf44b-4fl5s -ns insecure-nginx
Sysdig is starting to capture system calls:
Node: ip-172-20-42-49.ec2.internal
Pod: insecure-nginx-7c99fdf44b-4fl5s
Duration: 120 seconds
Parameters for Sysdig: -S -M 120 -pk -z -w /capture-insecure-nginx-7c99fdf44b-4fl5s-1587337260.scap.gz
The capture has been downloaded to your hard disk at:
/Users/kaizhehuang/demo/chapter11/sysdig/capture-insecure-nginx-7c99fdf44b-4fl5s-1587337260.scap.gz

在幕后,kubectl-capture在运行疑似受害者 pod 的主机上启动一个新的 pod 进行捕获,持续时间为120秒,这样我们就可以看到主机上正在发生的一切以及接下来120秒内的情况。捕获完成后,压缩的捕获文件将在当前工作目录中创建。您可以将 Sysdig Inspect 作为 Docker 容器引入,以开始安全调查,就像这样:

$ docker run -d -v /Users/kaizhehuang/demo/chapter11/sysdig:/captures -p3000:3000 sysdig/sysdig-inspect:latest
17533f98a947668814ac6189908ff003475b10f340d8f3239cd3627fa9747769

现在,登录到http://localhost:3000,您应该看到登录用户界面UI)。记得解压scap文件,这样您就可以看到捕获文件的概述页面,如下所示:

图 11.5 - Sysdig Inspect 概述

图 11.5 - Sysdig Inspect 概述

Sysdig Inspect 从以下角度提供了对容器内发生活动的全面洞察:

  • 执行的命令

  • 文件访问

  • 网络连接

  • 系统调用

让我们不仅仅限于 Falco 警报进行更深入的挖掘。根据警报,我们可能怀疑这是一个文件路径遍历问题,因为是nginx进程访问/etc/passwd文件,我们知道这个 pod 只提供静态文件服务,所以nginx进程不应该访问/usr/share/nginx/html/目录之外的任何文件。现在,让我们看一下以下截图,看看发送给nginx pod 的网络请求是什么:

图 11.6 – Sysdig Inspect 调查连接到 nginx 的网络连接

图 11.6 – Sysdig Inspect 调查连接到 nginx 的网络连接

在查看连接后,我们发现请求来自单个 IP,100.123.226.66,看起来像是一个 pod IP。它可能来自同一个集群吗?在左侧面板上点击Containers视图,并在过滤器中指定fd.cip=100.123.226.66。然后,你会发现它来自anchore-cli容器,如下截图所示:

图 11.7 – Sysdig Inspect 调查一个容器向 nginx 发送请求

图 11.7 – Sysdig Inspect 调查一个容器向 nginx 发送请求

事实上,anchore-cli pod 碰巧运行在与nginx pod 相同的节点上,如下面的代码块所示:

$ kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP               NODE                           NOMINATED NODE   READINESS GATES
anchore-cli   1/1     Running   1          77m   100.123.226.66   ip-172-20-42-49.ec2.internal   <none>           <none>
$ kubectl get pods -n insecure-nginx -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE                           NOMINATED NODE   READINESS GATES
insecure-nginx-7c99fdf44b-4fl5s   1/1     Running   0          78m   100.123.226.65   ip-172-20-42-49.ec2.internal   <none>           <none>

现在我们知道可能有一些文件路径遍历攻击是从anchore-cli pod 发起的,让我们看看这是什么(只需在前面的Sysdig Inspect页面中双击条目),如下所示:

图 11.8 – Sysdig Inspect 调查路径遍历攻击命令

图 11.8 – Sysdig Inspect 调查路径遍历攻击命令

我们发现在anchore-cli pod 中执行了一系列文件路径遍历命令,详细如下:

  • 使用 curl 命令访问 100.71.138.95 上的文件../etc/

  • 使用 curl 命令访问 100.71.138.95 上的文件../

  • 使用 curl 命令访问 100.71.138.95 上的文件../etc/passwd

  • 使用 curl 命令访问 100.71.138.95 上的文件../etc/shadow

我们现在能够更接近攻击者了,下一步是尝试更深入地调查攻击者是如何进入anchore-cli pod 的。

CRIU 和 Sysdig 都是在容器化环境中进行取证的强大工具。希望 CRIU 问题能够很快得到解决。请注意,CRIU 还需要 Docker 守护程序以experimental模式运行,而 Sysdig 和 Sysdig Inspect 更多地在 Kubernetes 级别工作。Sysdig Inspect 提供了一个漂亮的用户界面,帮助浏览发生在 Pod 和容器中的不同活动。

总结

在这一长章中,我们涵盖了 Kubernetes 审计、Kubernetes 集群的高可用性、使用 Vault 管理秘密、使用 Falco 检测异常活动以及使用 CRIU 和 Sysdig 进行取证。虽然您可能会发现需要花费相当长的时间来熟悉所有的实践和工具,但深度防御是一个庞大的主题,值得深入研究安全性,这样您就可以为 Kubernetes 集群建立更强大的防护。

我们谈到的大多数工具都很容易安装和部署。我鼓励您尝试它们:添加自己的 Kubernetes 审计规则,使用 Vault 在 Kubernetes 集群中管理秘密,编写自己的 Falco 规则来检测异常行为,因为您比任何其他人都更了解您的集群,并使用 Sysdig 收集所有取证数据。一旦您熟悉了所有这些工具,您应该会对自己的 Kubernetes 集群更有信心。

在下一章中,我们将讨论一些已知的攻击,比如针对 Kubernetes 集群的加密挖矿攻击,看看我们如何利用本书中学到的技术来减轻这些攻击。

问题

  1. 为什么我们不应该将审计级别设置为RequestRequestResponse用于秘密对象?

  2. kops中用什么标志设置多个主节点?

  3. 当 Vault 中的秘密更新时,侧车容器会做什么?

  4. Falco 使用哪些事件源?

  5. Falco 使用哪个过滤器从系统调用事件中检索进程名称?

  6. CRIU 对正在运行的容器有什么作用?

  7. 您可以用 Sysdig Inspect 做什么?

更多参考资料

第三部分:从错误和陷阱中学习

在本节中,您将了解涉及 Kubernetes 集群的一些攻击场景,从已知攻击和 CVE 到缓解和预防策略。

本节包括以下章节:

  • 第十二章, 分析和检测加密货币挖矿攻击

  • 第十三章, 从 Kubernetes CVEs 中学习

第十二章:分析和检测加密挖矿攻击

随着区块链和加密货币的日益普及,加密挖矿攻击变得越来越引人注目。加密货币是通过在区块链上利用计算资源进行去中心化交易的交易费而获得的。使用计算资源验证交易以赚取加密货币的过程称为加密挖矿,由一个名为加密挖矿软件的软件进行。安全研究人员发现与各种加密挖矿二进制文件相关的黑客事件在受害者的基础设施内运行。Kubernetes 集群的默认开放性以及用于挖矿所需的大量计算能力的可用性使得 Kubernetes 集群成为加密挖矿攻击的完美目标。Kubernetes 集群的复杂性也使得加密挖矿活动难以检测。

由于我们已经介绍了不同的 Kubernetes 内置安全机制和开源工具来保护 Kubernetes 集群,现在我们将看看如何在具体场景中使用它们。在本章中,我们将首先分析几种已知的加密挖矿攻击,然后我们将讨论如何使用开源工具检测加密挖矿攻击的检测机制。最后但同样重要的是,我们将回顾我们在之前章节中讨论的主题,并看看它们应该如何应用来保护我们的环境免受一般攻击。

本章将涵盖以下主题:

  • 分析加密挖矿攻击

  • 检测挖矿攻击

  • 防御攻击

分析加密挖矿攻击

在本节中,我们将首先简要介绍加密挖矿攻击,然后分析一些公开披露的加密挖矿攻击。我们希望您了解加密挖矿攻击模式以及使攻击可能的缺陷。

加密挖矿攻击简介

区块链构成了加密货币的基础。简而言之,区块链是由表示为区块的数字资产链组成的。这些区块包含有关交易的信息以及谁参与了交易的数字签名。每种加密货币都与一个区块链相关联。验证交易记录的过程称为挖矿。挖矿将历史记录添加到区块链中,以确保区块在未来无法修改。挖矿旨在消耗大量资源,以确保区块链的去中心化属性。通过成功挖矿区块,矿工可以获得与交易相关的交易费。因此,如果你有一台笔记本电脑或个人电脑,你也可以用它来挖矿;但很可能你需要一些专用的 GPU 或专门的硬件,比如现场可编程门阵列FPGA)和专用集成电路ASIC)来做好挖矿工作。Kubernetes 集群中的资源可用性使它们成为攻击者赚取加密货币的理想目标。

加密挖矿攻击就像在 Wi-Fi 上免费搭车一样。就像你的网络带宽会被免费搭车者分享一样,你的 CPU 或计算资源的一部分(或大部分)将在没有你的同意的情况下被挖矿进程占用。影响也是类似的。如果 Wi-Fi 上的免费搭车者正在使用你的 Wi-Fi 网络通过 BitTorrent 下载电影,你在观看 Netflix 时可能会有不好的体验。当有挖矿进程运行时,同一节点中运行的其他应用程序也会受到严重影响,因为挖矿进程可能会大部分时间占用 CPU。

加密挖矿攻击已经成为黑客最吸引人的攻击之一,因为这几乎是一种确保能够从成功入侵中获益的方式。小偷只来偷或破坏。如果破坏不是入侵的目的,加密挖矿攻击可能是黑客的主要选择之一。

黑客发动加密挖矿攻击的至少两种方式已经被报道。一种是通过应用程序漏洞,比如跨站脚本,SQL 注入,远程代码执行等,使黑客能够访问系统,然后下载并执行挖矿程序。另一种方式是通过恶意容器镜像。当从包含挖矿程序的镜像创建容器时,挖矿过程就会开始。

尽管互联网上有不同类型的加密挖矿二进制文件,但总的来说,矿业过程是计算密集型的,占用大量 CPU 周期。矿业过程有时会加入矿业池,以便以合作的方式进行挖矿。

接下来,让我们看看发生在现实世界中的一些加密挖矿攻击。我们将讨论使攻击可能的漏洞,并研究攻击模式。

特斯拉的 Kubernetes 集群上的加密挖矿攻击

2018 年,特斯拉的 Kubernetes 集群遭受了一次加密挖矿攻击,并由 RedLock 报告。尽管这次攻击发生了相当长时间,但我们至少可以从中学到两件事情——使攻击可能的漏洞和攻击模式。

漏洞

黑客渗透了没有密码保护的 Kubernetes 仪表板。从仪表板上,黑客获得了一些重要的秘密来访问 Amazon S3 存储桶。

攻击模式

黑客们做了相当不错的工作,隐藏了他们的足迹,以避免被发现。以下是一些值得一提的模式:

  • 矿业过程没有占用太多 CPU 周期,因此 pod 的 CPU 使用率并不太高。

  • 与大多数加密挖矿案例不同,矿业过程没有加入任何知名的矿业池。相反,它有自己的矿业服务器,位于 Cloudflare 后面,这是一个内容交付网络(CDN)服务。

  • 矿业过程与矿业服务器之间的通信是加密的。

通过前面的操作,黑客故意试图隐藏加密挖矿模式,以便逃避检测。

Graboid——一次加密蠕虫攻击

这次加密蠕虫攻击是由 Palo Alto Network Unit42 研究团队在 2019 年底发现的。尽管这次攻击并不是针对 Kubernetes 集群,但它是针对 Docker 守护程序的,这是 Kubernetes 集群中的基石之一。在攻击的一个步骤中,工具包从 Docker Hub 下载包含加密挖矿二进制文件的镜像并启动。这一步也可以应用于 Kubernetes 集群。

漏洞

Docker 引擎暴露在互联网上,而且没有进行身份验证和授权配置。攻击者可以轻松地完全控制 Docker 引擎。

攻击模式

一旦黑客控制了 Docker 引擎,他们就开始下载一个恶意镜像并启动一个容器。以下是关于恶意容器值得一提的一些模式:

  • 恶意容器联系了命令和控制服务器,以下载一些恶意脚本。

  • 恶意容器包含了一个 Docker 客户端二进制文件,用于控制其他不安全的 Docker 引擎。

  • 恶意容器通过 Docker 客户端向其他不安全的 Docker 引擎发出命令,以下载和启动另一个包含加密挖矿二进制文件的镜像。

根据 Shodan 的数据,超过 2000 个 Docker 引擎暴露在互联网上。前述步骤被重复执行,以便加密挖矿蠕虫传播。

吸取的教训

回顾一下我们讨论过的两种已知的加密挖矿攻击,配置错误是使黑客轻松入侵的主要问题之一。加密挖矿具有一些典型的模式,例如,挖矿过程将与挖矿池通信,而挖矿过程通常会占用大量的 CPU 周期。然而,黑客可能会故意伪装他们的挖矿行为以逃避检测。一旦黑客进入 pod,他们可以开始联系命令和控制服务器来下载和执行挖矿二进制文件;另一方面,他们也可以开始侦察。如果您的 Kubernetes 集群中的安全域没有得到适当配置,他们很容易进行横向移动。接下来,让我们使用我们在之前章节介绍的开源工具来检测 Kubernetes 集群中典型的加密挖矿活动。

检测加密挖矿攻击

在这一部分,我们将讨论如何使用我们在前几章介绍的一些开源工具来检测 Kubernetes 集群中的加密挖矿活动。我们基于已知的加密挖矿模式来检测加密挖矿活动:高 CPU 使用率,与挖矿池的通信,矿工的执行命令行以及二进制签名。请注意,每个单独的措施都有其自身的局限性。将它们结合起来可以提高检测的效率。然而,仍然存在一些高级的加密挖矿攻击,比如攻击特斯拉的那种。因此,有必要与安全团队合作,为您的 Kubernetes 集群应用全面的检测策略,以覆盖各种入侵。

为了演示每个工具检测加密挖矿,我们模拟一个受害者nginx pod:

$ kubectl get pods -n insecure-nginx
NAME                              READY   STATUS    RESTARTS   AGE
insecure-nginx-8455b6d49c-z6wb9   1/1     Running   0          163m

nginx pod 内部,有一个矿工二进制文件位于/tmp目录中:

root@insecure-nginx-8455b6d49c-z6wb9:/# ls /tmp
minerd2  perg

minerd2是挖矿二进制文件。我们可以假设minerd2要么被种子化在镜像中,要么从命令和控制服务器下载。首先,让我们看看监控 CPU 使用率如何帮助检测加密挖矿活动。

注意

不建议在生产服务器上运行加密挖矿二进制文件。这仅供教育目的。

监控 CPU 利用率

正如我们在第十章中讨论的那样,Kubernetes 集群的实时监控和资源管理,资源管理和资源监控对于维护服务的可用性至关重要。加密挖矿通常占用大量 CPU 周期,导致容器或 pod 的 CPU 使用率显着提高。让我们通过比较加密挖矿发生前后nginx pod 的 CPU 使用情况来看一个例子:

图 12.1 - 挖矿前 nginx pod 的 CPU 使用情况在 Grafana 指标中

图 12.1 - 挖矿前 nginx pod 的 CPU 使用情况在 Grafana 指标中

前面的截图显示了由 Prometheus 和 Grafana 监控的insecure-nginx pod 的 CPU 使用情况。一般来说,最大的 CPU 使用率小于0.1。当执行加密挖矿二进制文件时,你会发现 CPU 使用率急剧上升:

图 12.2 - 挖矿后 nginx pod 的 CPU 使用情况

图 12.2 - 挖矿后 nginx pod 的 CPU 使用情况

CPU 使用率从平均率0.07上升到约2.4。无论在幕后发生了什么,这样巨大的 CPU 使用率上升都应立即引起您的注意。很明显,即使有这样的 CPU 激增,也不意味着 pod 内运行着加密挖矿二进制文件。CPU 激增也可能是由其他原因引起的。

另一方面,如果黑客故意限制加密挖矿攻击的进展,就像对特斯拉的攻击一样,CPU 可能只会有一点点上升,很难注意到。接下来,让我们看看 Falco 如何帮助检测加密挖矿活动。

检测到矿池的网络流量

典型的加密挖矿进程行为是挖矿进程与同一挖矿池内的其他挖矿进程协作,以便高效地进行挖矿。挖矿进程在挖矿期间与挖矿池服务器进行通信。

在 Falco 的默认规则中,有一个规则用于检测对已知矿工池的出站连接。让我们更仔细地看看这个规则。首先,有一个用于挖矿端口和挖矿域的预定义列表(github.com/falcosecurity/falco/blob/master/rules/falco_rules.yaml#L2590):

- list: miner_ports
  items: [
        25, 3333, 3334, 3335, 3336, 3357, 4444,
        5555, 5556, 5588, 5730, 6099, 6666, 7777,
        7778, 8000, 8001, 8008, 8080, 8118, 8333,
        8888, 8899, 9332, 9999, 14433, 14444,
        45560, 45700
    ]
- list: miner_domains
  items: [
      "Asia1.ethpool.org","ca.minexmr.com", "monero.crypto-pool.fr",
      ...
      "xmr-jp1.nanopool.org","xmr-us-east1.nanopool.org",
      "xmr-us-west1.nanopool.org","xmr.crypto-pool.fr",
      "xmr.pool.minergate.com"
      ]

然后,有一个预定义的网络连接宏用于前述矿工端口和矿工域:

- macro: minerpool_other
  condition: (fd.sport in (miner_ports) and fd.sip.name in (miner_domains))

除了minerpool_other宏之外,还有两个分别用于 HTTP 和 HTTPS 连接的其他宏—minerpool_httpminerpool_https—它们都结合起来得到主要的检测逻辑:

- macro: net_miner_pool
  condition: (evt.type in (sendto, sendmsg) and evt.dir=< and (fd.net != "127.0.0.0/8" and not fd.snet in (rfc_1918_addresses)) and ((minerpool_http) or (minerpool_https) or (minerpool_other)))

然后,net_miner_pool宏由检测出站连接到常见矿工池端口规则使用,以检测出站连接到矿工域:

# The rule is disabled by default.
# Note: Falco will send DNS requests to resolve miner pool domains which may trigger alerts in your environment.
- rule: Detect outbound connections to common miner pool ports
  desc: Miners typically connect to miner pools on common ports.
  condition: net_miner_pool and not trusted_images_query_miner_domain_dns
  enabled: true
  output: Outbound connection to IP/Port flagged by cryptoioc.ch (command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository)
  priority: CRITICAL
  tags: [network, mitre_execution]

如果有一个正在运行并与列表中定义的矿工域进行通信的加密挖矿进程,警报将被触发,如下所示:

19:46:37.939287649: Critical Outbound connection to IP/Port flagged by cryptoioc.ch (command=minerd2 -a cryptonight -o stratum+tcp://monero.crypto-pool.fr:3333 -u 49TfoHGd6apXxNQTSHrMBq891vH6JiHmZHbz5Vx36nLRbz6WgcJunTtgcxno G6snKFeGhAJB5LjyAEnvhBgCs5MtEgML3LU -p x port=37110 ip=100.97.244.198 container=k8s.ns=insecure-nginx k8s.pod=insecure-nginx-8455b6d49c-z6wb9 container=07dce07d5100 image=kaizheh/victim) k8s.ns=insecure-nginx k8s.pod=insecure-nginx-8455b6d49c-z6wb9 container=07dce07d5100 k8s.ns=insecure-nginx k8s.pod=insecure-nginx-8455b6d49c-z6wb9 container=07dce07d5100

检测出站连接到常见矿工池端口规则很简单。如果这个规则生成了一个警报,你应该把它作为高优先级处理。规则的限制也很明显;您将不得不保持挖矿域和挖矿端口的更新。如果有新的挖矿域可用或者使用了新的挖矿服务器端口,并且它们没有添加到 Falco 列表中,那么规则将无法检测到加密挖矿活动。请注意,该规则默认情况下是禁用的。由于 Falco 需要发送 DNS 请求来解析矿工池域名,这些 DNS 请求将被一些云提供商警报。一个副作用是,像 Cilium 的 Hubble 这样的开源工具可以帮助监控网络流量。

另一种方法是使用白名单方法。如果您知道微服务的出站连接中的目标端口或 IP 块,您可以创建 Falco 规则来警报不在白名单上的任何出站连接的目标 IP 或端口。以下是一个例子:

- list: trusted_server_addresses
  items: [...]
- list: trusted_server_ports
  items: [...]
- rule: Detect anomalous outbound connections 
  desc: Detect anomalous outbound connections
  condition: (evt.type in (sendto, sendmsg) and container and evt.dir=< and (fd.net != "127.0.0.0/8" and not fd.snet in (trusted_server_addresses) or not fd.sport in (trusted_server_ports))) 
  output: Outbound connection to anomalous IP/Port(command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository)
  priority: CRITICAL

上述规则将警报任何对trusted_server_portstrusted_server_addresses之外的 IP 地址或端口的出站连接。鉴于攻击发生在特斯拉,Falco 将警报存在异常连接,即使 IP 地址看起来正常。接下来,让我们看另一个 Falco 规则,根据命令行中的模式来检测潜在的加密挖矿活动。

检测已启动的加密挖矿进程

Stratum 挖矿协议是与挖矿服务器进行通信的挖矿过程中最常见的协议。一些挖矿二进制文件允许用户在执行时指定与挖矿池服务器通信的协议。

在 Falco 的默认规则中,有一个规则是基于命令行中的关键字来检测加密二进制文件的执行:

- rule: Detect crypto miners using the Stratum protocol
  desc: Miners typically specify the mining pool to connect to with a URI that begins with 'stratum+tcp'
  condition: spawned_process and proc.cmdline contains "stratum+tcp"
  output: Possible miner running (command=%proc.cmdline container=%container.info image=%container.image.repository)
  priority: CRITICAL
  tags: [process, mitre_execution]

如果 Falco 检测到任何使用stratum+tcp启动的进程并且在进程的命令行中指定了,那么检测使用 Stratum 协议的加密矿工规则将引发警报。输出如下:

19:46:37.779784798: Critical Possible miner running (command=minerd2 -a cryptonight -o stratum+tcp://monero.crypto-pool.fr:3333 -u 49TfoHGd6apXxNQTSHrMBq891vH6JiHmZHbz5Vx36 nLRbz6WgcJunTtgcxnoG6snKFeGhAJB5LjyAEnvhBgCs5MtEgML3LU -p x container=k8s.ns=insecure-nginx k8s.pod=insecure-nginx-8455b6d49c-z6wb9 container=07dce07d5100 image=kaizheh/victim) k8s.ns=insecure-nginx k8s.pod=insecure-nginx-8455b6d49c-z6wb9 container=07dce07d5100 k8s.ns=insecure-nginx k8s.pod=insecure-nginx-8455b6d49c-z6wb9 container=07dce07d5100

执行的minerd2 -a cryptonight -o stratum+tcp://monero.crypto-pool.fr:3333 -u 49TfoHGd6apXxNQTSHrMBq891vH6JiHmZHbz5Vx36nLRbz6Wgc JunTtgcxnoG6snKFeGhAJB5LjyAEnvhBgCs5MtEgML3LU -p x命令行包含了stratum+tcp关键字。这就是为什么会触发警报。

与其他基于名称的检测规则一样,该规则的限制是显而易见的。如果加密二进制执行文件不包含stratum+tcp,则该规则将不会被触发。

上述规则使用了黑名单方法。另一种方法是使用白名单方法,如果您知道将在微服务中运行的进程。您可以定义一个 Falco 规则,当启动任何不在信任列表上的进程时引发警报。以下是一个示例:

- list: trusted_nginx_processes
  items: ["nginx"]
- rule: Detect Anomalous Process Launched in Nginx Container
  desc: Anomalous process launched inside container.
  condition: spawned_process and container and not proc.name in (trusted_nginx_processes) and image.repository.name="nginx"
  output: Anomalous process running in Nginx container (command=%proc.cmdline container=%container.info image=%container.image.repository)
  priority: CRITICAL
  tags: [process]

上述规则将警报任何在nginx容器中启动的异常进程,其中包括加密挖矿进程。最后,让我们看看图像扫描工具如何通过与恶意软件源集成来帮助检测加密挖矿二进制文件的存在。

检查二进制签名

加密挖矿二进制文件有时可以被识别为恶意软件。与传统的反病毒软件一样,我们也可以检查运行中的二进制文件的哈希值与恶意软件源的匹配情况。借助图像扫描工具,比如 Anchore,我们可以获取文件的哈希值:

root@anchore-cli:/# anchore-cli --json image content kaizheh/victim:nginx files | jq '.content | .[] | select(.filename=="/tmp/minerd2")'
{
  "filename": "/tmp/minerd2",
  "gid": 0,
  "linkdest": null,
  "mode": "00755",
  "sha256": "e86db6abf96f5851ee476eeb8c847cd73aebd0bd903827a362 c07389d71bc728",
  "size": 183048,
  "type": "file",
  "uid": 0
}

/tmp/minerd2文件的哈希值为e86db6abf96f5851ee476eeb8c847cd73aebd0bd903827a362c07389d71bc728。然后,我们可以将哈希值与 VirusTotal 进行比对,VirusTotal 提供恶意软件信息源服务:

$ curl -H "Content-Type: application/json" "https://www.virustotal.com/vtapi/v2/file/report?apikey=$VIRUS_FEEDS_API_KEY&resource=e86db6abf96f5851ee476eeb8c847cd73aebd0bd903827a 362c07389d71bc728" | jq .

$VIRUS_FEEDS_API_KEY是您访问 VirusTotal API 服务的 API 密钥,然后提供以下报告:

{
  "scans": {
    "Fortinet": {
      "detected": true,
      "version": "6.2.142.0",
      "result": "Riskware/CoinMiner",
      "update": "20200413"
    },
    ...
    "Antiy-AVL": {
      "detected": true,
      "version": "3.0.0.1",
      "result": "RiskWare[RiskTool]/Linux.BitCoinMiner.a",
      "update": "20200413"
    },
  },
  ...
  "resource": "e86db6abf96f5851ee476eeb8c847cd73aebd0bd903827a362c07389d71bc 728",
  "scan_date": "2020-04-13 18:22:56",
  "total": 60,
  "positives": 25,
  "sha256": "e86db6abf96f5851ee476eeb8c847cd73aebd0bd903827a362c07389d71bc 728",
 }

VirusTotal 报告显示,/tmp/minerd2已被 25 个不同的信息源报告为恶意软件,如 Fortinet 和 Antiy AVL。通过在 CI/CD 流水线中集成图像扫描工具和恶意软件信息源服务,您可以帮助在开发生命周期的早期阶段检测恶意软件。然而,这种单一方法的缺点是,如果挖矿二进制文件从命令和控制服务器下载到运行的 Pod 中,您将错过加密挖矿攻击。另一个限制是,如果信息源服务器没有关于加密二进制文件的任何信息,您肯定会错过它。

我们已经讨论了四种不同的方法来检测加密挖矿攻击。每种方法都有其自身的优点和局限性;将一些这些方法结合起来以提高其检测能力和检测效果将是理想的。

接下来,让我们回顾一下我们在本书中讨论的内容,并全面运用这些知识来预防一般性的攻击。

防御攻击

在前一节中,我们讨论了几种检测加密挖矿活动的方法。在本节中,我们将讨论通过保护 Kubernetes 集群来防御攻击。因此,这不仅涉及防御特定攻击,还涉及防御各种攻击。四个主要的防御领域是 Kubernetes 集群供应、构建、部署和运行时。首先,让我们谈谈保护 Kubernetes 集群供应。

保护 Kubernetes 集群供应

有多种方法可以配置 Kubernetes 集群,比如kopskubeadm。无论您使用哪种工具来配置集群,每个 Kubernetes 组件都需要进行安全配置。使用kube-bench来对您的 Kubernetes 集群进行基准测试,并改进安全配置。确保启用了 RBAC,禁用了--anonymous-auth标志,网络连接进行了加密等等。以下是我们在第六章中涵盖的关键领域,保护集群组件,以及第七章身份验证、授权和准入控制

  • 为 Kubernetes 控制平面、kubelet等正确配置身份验证和授权

  • 保护 Kubernetes 组件之间的通信,例如kube-apiserverkubeletkube-apiserveretcd之间的通信

  • etcd启用静态数据加密

  • 确保不启动不必要的组件,比如仪表板

  • 确保所有必要的准入控制器都已启用,而已弃用的控制器已禁用

通过安全配置 Kubernetes 集群,可以减少黑客轻易入侵您的 Kubernetes 集群的机会,就像特斯拉的集群一样(其中仪表板不需要身份验证)。接下来,让我们谈谈如何保护构建。

保护构建

保护 Kubernetes 集群还包括保护微服务。保护微服务必须从 CI/CD 流水线的开始进行。以下是一些关键的对策,如第八章中讨论的,保护 Kubernetes Pod,以及第九章DevOps 流水线中的图像扫描,以在构建阶段保护微服务:

  • 妥善处理图像扫描工具发现的微服务漏洞,以减少通过利用应用程序漏洞成功入侵的可能性。

  • 对 Dockerfile 进行基准测试,以改进镜像的安全配置。确保镜像中没有存储敏感数据,所有依赖包都已更新等等。

  • 扫描镜像中的可执行文件,确保没有恶意软件植入镜像。

  • 为工作负载正确配置 Kubernetes 安全上下文。遵循最小特权原则,限制对系统资源的访问,比如使用主机级别的命名空间、主机路径等,并移除不必要的 Linux 能力,只授予必需的能力。

  • 不要启用自动挂载服务账户。如果工作负载不需要服务账户,就不要为其创建服务账户。

  • 遵循最小特权原则,尝试了解工作负载正在执行的任务,并只授予服务账户所需的特权。

  • 遵循最小特权原则,尝试估计工作负载的资源使用情况,并为工作负载应用适当的资源请求和限制。

当然,保护构建也可以扩展到保护整个 CI/CD 流水线,比如源代码管理和 CI/CD 组件。然而,这超出了本书的范围。我们只会建议我们认为最相关的保护 Kubernetes 集群的选项。接下来,让我们谈谈保护部署。

保护部署

我们已经在 Kubernetes 集群中的第七章认证、授权和准入控制,以及第八章保护 Kubernetes Pods中讨论了不同类型的准入控制器,以及正确使用它们的必要性,例如一个镜像扫描准入控制器的示例(第九章DevOps 流水线中的镜像扫描)。使用准入控制器和其他内置机制作为工作负载的重要安全门卫。以下是一些关键的对策:

  • 为命名空间和工作负载应用网络策略。这可以是限制对工作负载的访问(入站网络策略),也可以是实施最小特权原则(出站网络策略)。当给定一个工作负载时,如果你知道出站连接的目标 IP 块,你应该为该工作负载创建一个网络策略来限制其出站连接。出站网络策略应该阻止任何超出白名单 IP 块的目的地流量,比如从命令和控制服务器下载加密挖矿二进制文件。

  • 使用Open Policy AgentOPA)来确保只有来自受信任的镜像仓库的镜像被允许在集群中运行。有了这个策略,OPA 应该阻止来自不受信任来源的镜像运行。例如,可能存在包含加密挖矿二进制文件的恶意镜像在 Docker Hub 中,因此您不应该将 Docker Hub 视为受信任的镜像仓库。

  • 使用镜像扫描准入控制器来确保只有符合扫描策略的镜像被允许在集群中运行。我们已经在[第九章](B15566_09_Final_ASB_ePub.xhtml#_idTextAnchor277)中谈到了这一点,DevOps 流水线中的镜像扫描。可能会发现新的漏洞,并且在部署工作负载时漏洞数据库将会更新。在部署之前进行扫描是必要的。

  • 使用 OPA 或 Pod 安全策略来确保具有受限 Linux 功能和对主机级命名空间、主机路径等受限访问权限的工作负载。

  • 最好在工作节点上启用 AppArmor,并为部署的每个镜像应用一个 AppArmor 配置文件。当工作负载部署时,会限制 AppArmor 配置文件,尽管实际的保护是在运行时发生的。一个很好的用例是构建一个 AppArmor 配置文件,以允许白名单内的进程运行,当您知道容器内运行的进程时,这样其他进程,比如加密挖矿进程,将被 AppArmor 阻止。

利用准入控制器的力量,为您的工作负载部署构建一个门卫。接下来,让我们谈谈在运行时保护工作负载。

保护运行时

很可能,您的 Kubernetes 集群是与黑客作战的前线。尽管我们讨论了不同的策略来保护构建和部署,但所有这些策略最终都旨在减少 Kubernetes 集群中的攻击面。您不能简单地闭上眼睛,假设您的 Kubernetes 集群一切都会好起来。这就是为什么我们在[第十章](B15566_10_Final_ASB_ePub.xhtml#_idTextAnchor305)中谈论资源监控,Kubernetes 集群的实时监控和资源管理,以及审计、秘钥管理、检测和取证在[第十一章](B15566_11_Final_ASB_ePub.xhtml#_idTextAnchor324)中,深度防御。总结一下在这两章中涵盖的内容,以下是保护运行时的关键对策:

  • 部署像 Prometheus 和 Grafana 这样的良好的监控工具,以监控 Kubernetes 集群中的资源使用情况。这对于确保服务的可用性至关重要,而且像加密货币挖矿这样的攻击可能会引发 CPU 使用率的激增。

  • 启用 Kubernetes 的审计策略以记录 Kubernetes 事件和活动。

  • 确保基础设施、Kubernetes 组件和工作负载的高可用性。

  • 使用像 Vault 这样的良好的秘密管理工具来管理和提供微服务的秘密。

  • 部署像 Falco 这样的良好的检测工具,以侦测 Kubernetes 集群中的可疑活动。

  • 最好有取证工具来收集和分析可疑事件。

你可能注意到保护微服务间通信并未被提及。服务网格是一个热门话题,可以帮助保障微服务及其间通信,但出于两个原因,本书未涵盖服务网格:

  • 服务网格会给工作负载和 Kubernetes 集群带来性能开销,因此它们还不是保障服务间通信的完美解决方案。

  • 从应用安全的角度来看,可以轻松地强制应用程序在 443 端口上监听,并使用 CA 签名证书进行加密通信。如果微服务还执行身份验证和授权,那么只有受信任的微服务才能访问授权资源。服务网格并非保障服务间通信的不可替代解决方案。

为了防御针对 Kubernetes 集群的攻击,我们需要从头到尾保护 Kubernetes 集群的供应、构建、部署和运行。它们都应被视为同等重要,因为你的防御力取决于最薄弱的环节。

总结

在本章中,我们回顾了过去两年发生的一些加密货币挖矿攻击,这引起了对保护容器化环境需求的广泛关注。然后,我们向你展示了如何使用不同的开源工具来检测加密货币挖矿攻击。最后但同样重要的是,我们讨论了如何通过总结前几章的内容来保护你的 Kubernetes 集群免受攻击。

我们希望你理解保护 Kubernetes 集群的核心概念,这意味着保护集群的供应、构建、部署和运行阶段。你也应该对开始使用 Anchore、Prometheus、Grafana 和 Falco 感到满意。

众所周知,Kubernetes 仍在不断发展,并不完美。在下一章中,我们将讨论一些已知的 Kubernetes常见漏洞和曝光CVEs)以及一些可以保护您的集群免受未知变体影响的缓解措施。以下一章的目的是为了让您能够应对未来发现的任何 Kubernetes CVEs。

问题

  • 是什么缺陷导致了特斯拉的 Kubernetes 集群中发生了加密挖矿攻击?

  • 如果您是特斯拉的 DevOps,您会采取什么措施来防止加密挖矿攻击?

  • 当您在一个容器中看到 CPU 使用率激增时,您能否得出结论说发生了加密挖矿攻击?

  • 您能想到一种可以绕过“使用 Stratum 协议检测加密挖矿程序”的 Falco 规则的加密挖矿过程吗?

  • 为了保护您的 Kubernetes 集群,您需要保护哪四个领域?

进一步阅读

有关本章涵盖的主题的更多信息,请参考以下链接:

第十三章:从 Kubernetes CVEs 中学习

通用漏洞和暴露CVEs)是对广为人知的安全漏洞和暴露的标识,这些漏洞和暴露存在于流行的应用程序中。CVE ID 由CVE字符串后跟漏洞的年份和 ID 号组成。CVE 数据库是公开可用的,并由 MITRE 公司维护。CVE 条目包括每个问题的简要描述,有助于了解问题的根本原因和严重程度。这些条目不包括问题的技术细节。CVE 对于 IT 专业人员协调和优先更新是有用的。每个 CVE 都有与之相关的严重性。MITRE 使用通用漏洞评分系统CVSS)为 CVE 分配严重性评级。建议立即修补高严重性的 CVE。让我们看一个在cve.mitre.org上的 CVE 条目的例子。

如下截图所示,CVE 条目包括 ID、简要描述、参考文献、CVE 编号管理机构CNA)的名称以及条目创建日期:

图 13.1 - CVE-2018-18264 的 MITRE 条目

图 13.1 - CVE-2018-18264 的 MITRE 条目

对于安全研究人员和攻击者来说,CVE 条目最有趣的部分是参考文献部分。CVE 的参考文献是指研究人员发布的博客链接,涵盖了问题的技术细节,以及问题描述和拉取请求的链接。安全研究人员研究这些参考文献,以了解漏洞并开发类似问题的缓解措施,或者针对尚未修复的已知问题。另一方面,攻击者研究这些参考文献,以找到未修补的问题变体。

在本章中,我们将讨论 Kubernetes 的四个公开已知安全漏洞。首先,我们将看一下路径遍历问题-CVE-2019-11246。这个问题允许攻击者修改客户端的内容,这可能导致数据泄露或在集群管理员的机器上执行代码。接下来,我们将讨论 CVE-2019-1002100,它允许用户对 API 服务器进行拒绝服务(DoS)攻击。然后,我们将讨论 CVE-2019-11253,它允许未经身份验证的用户对kube-apiserver进行 DoS 攻击。最后,我们将讨论 CVE-2019-11247,它允许具有命名空间权限的用户修改集群范围的资源。我们将讨论每个 CVE 的缓解策略。升级到 Kubernetes 和kubectl的最新版本,以修补漏洞,应该是您的首要任务。Kubernetes 的最新稳定版本可以在github.com/kubernetes/kubernetes/releases找到。我们将讨论的缓解策略将有助于加强您的集群对类似性质的攻击。最后,我们将介绍kube-hunter,它可以用于扫描已知安全漏洞的 Kubernetes 集群。

在本章中,我们将涵盖以下主题:

  • kubectl cp 中的路径遍历问题-CVE-2019-11246

  • JSON 解析中的 DoS 问题-CVE-2019-1002100

  • YAML 解析中的 DoS 问题-CVE-2019-11253

  • 角色解析中的特权升级问题-CVE-2019-11247

  • 使用 kube-hunter 扫描已知漏洞

kubectl cp 中的路径遍历问题-CVE-2019-11246

开发人员经常为调试目的将文件复制到或从 Pod 中的容器中。 kubectl cp允许开发人员从 Pod 中的容器复制文件,或者将文件复制到 Pod 中的容器(默认情况下,这是在 Pod 中的第一个容器中完成的)。

要将文件复制到 Pod,您可以使用以下方法:

kubectl cp /tmp/test <pod>:/tmp/bar

要从 Pod 复制文件,您可以使用以下方法:

kubectl cp <some-pod>:/tmp/foo /tmp/bar

当文件从一个 pod 中复制时,Kubernetes 首先创建文件内部的文件的 TAR 归档。然后将 TAR 归档复制到客户端,最后为客户端解压 TAR 归档。2018 年,研究人员发现了一种方法,可以使用kubectl cp来覆盖客户端主机上的文件。如果攻击者可以访问一个 pod,这个漏洞可以被用来用恶意文件替换 TAR 归档。当畸形的 TAR 文件被复制到主机时,它可以在解压时覆盖主机上的文件。这可能导致数据泄露和主机上的代码执行。

让我们看一个例子,攻击者修改 TAR 归档,使其包含两个文件:regular.txtfoo/../../../../bin/ps。在这个归档中,regular.txt是用户期望的文件,ps是一个恶意二进制文件。如果这个归档被复制到/home/user/admin,恶意二进制文件将覆盖bin文件夹中的众所周知的ps二进制文件。这个问题的第一个补丁是不完整的,攻击者找到了一种利用符号链接的方法来利用相同的问题。研究人员找到了一种绕过符号链接修复的方法,最终在 1.12.9、1.13.6 和 1.14.2 版本中解决了这个问题,并被分配了 CVE-2019-11246。

缓解策略

您可以使用以下策略来加固您的集群,以防止这个问题和类似于 CVE-2019-11246 的问题:

  • 始终使用更新版本的 kubectl:您可以使用以下命令找到kubectl二进制文件的最新版本:
$ curl https://storage.googleapis.com/kubernetes-release/release/stable.txt
v1.18.3
  • 使用准入控制器限制 kubectl cp 的使用:正如我们在第七章中讨论的那样,身份验证、授权和准入控制,Open Policy Agent 可以用作准入控制器。让我们看一个拒绝调用kubectl cp的策略:
deny[reason] {
  input.request.kind.kind == "PodExecOptions"
  input.request.resource.resource == "pods"
  input.request.subResource == "exec"
  input.request.object.command[0] == "tar"
  reason = sprintf("kubectl cp was detected on %v/%v by user: %v", [
    input.request.namespace,
    input.request.object.container,
    input.request.userInfo.username])
}

这个策略拒绝了 pod 中 TAR 二进制文件的执行,从而禁用了所有用户的kubectl cp。您可以更新此策略,以允许特定用户或组的kubectl cp

  • 为客户端应用适当的访问控制:如果您是生产集群的管理员,您的工作机器上有许多攻击者可能想要访问的机密信息。理想情况下,构建机器不应该是您的工作笔记本电脑。管理员可以ssh到的专用硬件来访问 Kubernetes 集群是一个良好的做法。您还应确保构建机器上的任何敏感数据都具有适当的访问控制。

  • 为所有 pod 设置安全上下文:如第八章中所讨论的,保护 Kubernetes Pod,确保 pod 具有readOnlyRootFilesystem,这将防止攻击者在文件系统中篡改文件(例如,覆盖/bin/tar二进制文件)。

spec:
    securityContext:
        readOnlyRootFilesystem: true

检测 pod 中二进制文件的修改:使用默认的 Falco 规则中的Write below monitored dir来检测对 TAR 二进制文件的更改:

- rule: Write below monitored dir
  desc: an attempt to write to any file below a set of binary directories
  condition: >
    evt.dir = < and open_write and monitored_dir
    and not package_mgmt_procs
    and not coreos_write_ssh_dir
    and not exe_running_docker_save
    and not python_running_get_pip
    and not python_running_ms_oms
    and not google_accounts_daemon_writing_ssh
    and not cloud_init_writing_ssh
    and not user_known_write_monitored_dir_conditions
  output: >
    File below a monitored directory opened for writing (user=%user.name
    command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
  priority: ERROR
  tags: [filesystem, mitre_persistence]

检测使用易受攻击的 kubectl 实例:kubectl版本 1.12.9、1.13.6 和 1.14.2 已修复了此问题。使用早于此版本的任何版本都将触发以下规则:

- macro: safe_kubectl_version
  condition: (jevt.value[/userAgent] startswith "kubectl/v1.15" or
              jevt.value[/userAgent] startswith "kubectl/v1.14.3" or
              jevt.value[/userAgent] startswith "kubectl/v1.14.2" or
              jevt.value[/userAgent] startswith "kubectl/v1.13.7" or
              jevt.value[/userAgent] startswith "kubectl/v1.13.6" or
              jevt.value[/userAgent] startswith "kubectl/v1.12.9")
# CVE-2019-1002101
# Run kubectl version --client and if it does not say client version 1.12.9,
1.13.6, or 1.14.2 or newer,  you are running a vulnerable version.
- rule: K8s Vulnerable Kubectl Copy
  desc: Detect any attempt vulnerable kubectl copy in pod
  condition: kevt_started and pod_subresource and kcreate and
             ka.target.subresource = "exec" and ka.uri.param[command] = "tar" and
             not safe_kubectl_version
  output: Vulnerable kubectl copy detected (user=%ka.user.name pod=%ka.target.name ns=%ka.target.namespace action=%ka.target.subresource command=%ka.uri.param[command] userAgent=%jevt.value[/userAgent])
  priority: WARNING
  source: k8s_audit
  tags: [k8s]

CVE-2019-11246 是为什么您需要跟踪安全公告并阅读技术细节以添加减轻策略到您的集群以确保如果发现问题的任何变化,您的集群是安全的一个很好的例子。接下来,我们将看看 CVE-2019-1002100,它可以用于在kube-apiserver上引起 DoS 问题。

JSON 解析中的 DoS 问题-CVE-2019-1002100

修补是一种常用的技术,用于在运行时更新 API 对象。开发人员使用kubectl patch在运行时更新 API 对象。一个简单的例子是向 pod 添加一个容器:

spec:
  template:
    spec:
      containers:
      - name: db
        image: redis

前面的补丁文件允许一个 pod 被更新以拥有一个新的 Redis 容器。kubectl patch允许补丁以 JSON 格式。问题出现在kube-apiserver的 JSON 解析代码中,这允许攻击者发送一个格式错误的json-patch实例来对 API 服务器进行 DoS 攻击。在第十章中,Kubernetes 集群的实时监控和资源管理,我们讨论了 Kubernetes 集群中服务可用性的重要性。这个问题的根本原因是kube-apiserverpatch请求的未经检查的错误条件和无限制的内存分配。

缓解策略

你可以使用以下策略来加固你的集群,以防止这个问题和类似 CVE-2019-100210 的问题:

  • 在 Kubernetes 集群中使用资源监控工具:如第十章中所讨论的,Kubernetes 集群的实时监控和资源管理,资源监控工具如 Prometheus 和 Grafana 可以帮助识别主节点内存消耗过高的问题。在 Prometheus 指标图表中,高数值可能如下所示:
container_memory_max_usage_bytes{pod_ name="kube-apiserver-xxx" }
sum(rate(container_cpu_usage_seconds_total{pod_name="kube-apiserver-xxx"}[5m]))
sum(rate(container_network_receive_bytes_total{pod_name="kube-apiserver-xxx"}[5m]))

这些资源图表显示了kube-apiserver在 5 分钟间隔内的最大内存、CPU 和网络使用情况。这些使用模式中的任何异常都是kube-apiserver受到攻击的迹象。

  • 建立高可用性的 Kubernetes 主节点:我们在第十一章中学习了高可用性集群,深度防御。高可用性集群有多个 Kubernetes 组件的实例。如果一个组件的负载很高,其他实例可以被使用,直到负载减少或第一个实例重新启动。

使用kops,你可以使用--master-zones={zone1, zone2}来拥有多个主节点:

kops create cluster k8s-clusters.k8s-demo-zone.com \
  --cloud aws \
  --node-count 3 \
  --zones $ZONES \
  --node-size $NODE_SIZE \
  --master-size $MASTER_SIZE \
  --master-zones $ZONES \
  --networking calico \
  --kubernetes-version 1.14.3 \
  --yes \
kube-apiserver-ip-172-20-43-65.ec2.internal              1/1     Running   4          4h16m
kube-apiserver-ip-172-20-67-151.ec2.internal             1/1     Running   4          4h15m

正如您所看到的,这个集群中有多个kube-apiserver pods 在运行。

  • 使用 RBAC 限制用户权限:用户的权限也应该遵循最小权限原则,这在第四章中已经讨论过,在 Kubernetes 中应用最小权限原则。如果用户不需要访问任何资源的PATCH权限,角色应该被更新以便他们没有访问权限。

  • 在暂存环境中测试您的补丁:暂存环境应设置为生产环境的副本。开发人员并不完美,因此开发人员可能会创建格式不正确的补丁。如果在暂存环境中测试集群的补丁或更新,就可以在不影响生产服务的情况下发现补丁中的错误。

DoS 通常被认为是低严重性问题,但如果发生在集群的核心组件上,您应该认真对待。对kube-apiserver的 DoS 攻击可能会破坏整个集群的可用性。接下来,我们将看看针对 API 服务器的另一种 DoS 攻击。未经身份验证的用户可以执行此攻击,使其比 CVE-2019-1002100 更严重。

YAML 解析中的 DoS 问题 - CVE-2019-11253

XML 炸弹或十亿笑攻击在任何 XML 解析代码中都很受欢迎。与 XML 解析问题类似,这是发送到kube-apiserver的 YAML 文件中的解析问题。如果发送到服务器的 YAML 文件具有递归引用,它会触发kube-apiserver消耗 CPU 资源,从而导致 API 服务器的可用性问题。在大多数情况下,由kube-apiserver解析的请求受限于经过身份验证的用户,因此未经身份验证的用户不应该能够触发此问题。在 Kubernetes 版本 1.14 之前的版本中有一个例外,允许未经身份验证的用户使用kubectl auth can-i来检查他们是否能执行操作。

这个问题类似于 CVE-2019-1002100,但更严重,因为未经身份验证的用户也可以触发此问题。

缓解策略

您可以使用以下策略来加固您的集群,以防止此问题和类似于 CVE-2019-11253 的尚未发现的问题:

  • 在 Kubernetes 集群中使用资源监控工具:类似于 CVE-2019-1002100,资源监控工具(如 Prometheus 和 Grafana)可以帮助识别主节点内存消耗过高的问题,我们在第十章中讨论了实时监控和资源管理的 Kubernetes 集群

  • 启用 RBAC:漏洞是由kube-apiserver在 YAML 文件中对递归实体的处理不当以及未经身份验证的用户与kube-apiserver交互的能力引起的。我们在第七章中讨论了 RBAC,身份验证、授权和准入控制。RBAC 在当前版本的 Kubernetes 中默认启用。您也可以通过将--authorization-mode=RBAC传递给kube-apiserver来启用它。在这种情况下,未经身份验证的用户不应被允许与kube-apiserver交互。对于经过身份验证的用户,应遵循最小特权原则。

  • 为未经身份验证的用户禁用 auth can-i(对于 v1.14.x):不应允许未经身份验证的用户与kube-apiserver交互。在 Kubernetes v1.14.x 中,您可以使用github.com/kubernetes/kubernetes/files/3735508/rbac.yaml.txt中的 RBAC 文件禁用未经身份验证的服务器的auth can-i

kubectl auth reconcile -f rbac.yaml --remove-extra-subjects --remove-extra-permissions
kubectl annotate --overwrite clusterrolebinding/system:basic-user rbac.authorization.kubernetes.io/autoupdate=false 

第二个命令禁用了clusterrolebinding的自动更新,这将确保在重新启动时不会覆盖更改。

  • kube-apiserver 不应暴露在互联网上:允许来自受信任实体的 API 服务器访问使用防火墙或 VPC 是一个良好的做法。

  • 禁用匿名身份验证:我们在第六章中讨论了anonymous-auth作为一个应该在可能的情况下禁用的选项,保护集群组件。匿名身份验证在 Kubernetes 1.16+中默认启用以用于传统策略规则。如果您没有使用任何传统规则,建议默认禁用anonymous-auth,方法是将--anonymous-auth=false传递给 API 服务器。

正如我们之前讨论的,对kube-apiserver的 DoS 攻击可能会导致整个集群的服务中断。除了使用包含此问题补丁的最新版本的 Kubernetes 之外,重要的是遵循这些缓解策略,以避免集群中出现类似问题。接下来,我们将讨论授权模块中触发经过身份验证用户特权升级的问题。

角色解析中的特权升级问题 – CVE-2019-11247

我们在第七章中详细讨论了 RBAC,认证、授权和准入控制。角色和角色绑定允许用户获得执行某些操作的特权。这些特权是有命名空间的。如果用户需要集群范围的特权,则使用集群角色和集群角色绑定。这个问题允许用户进行集群范围的修改,即使他们的特权是有命名空间的。准入控制器的配置,比如 Open Policy Access,可以被具有命名空间角色的用户修改。

缓解策略

您可以使用以下策略来加固您的集群,以防止这个问题和类似 CVE-2019-11247 的问题:

  • 避免在角色和角色绑定中使用通配符:角色和集群角色应该特定于资源名称、动词和 API 组。在 roles 中添加 * 可以允许用户访问他们本不应该访问的资源。这符合我们在第四章中讨论的最小特权原则,在 Kubernetes 中应用最小特权原则

  • 启用 Kubernetes 审计:我们在第十一章中讨论了 Kubernetes 的审计和审计策略,深度防御。Kubernetes 的审计可以帮助识别集群中的任何意外操作。在大多数情况下,这样的漏洞会被用来修改和删除集群中的任何额外控制。您可以使用以下策略来识别这类利用的实例:

  apiVersion: audit.k8s.io/v1 # This is required.
      kind: Policy
      rules:
      - level: RequestResponse
        verbs: ["patch", "update", "delete"]
        resources:
        - group: ""
          resources: ["pods"]
          namespaces: ["kube-system", "monitoring"]

此策略记录了在 kube-systemmonitoring 命名空间中删除或修改 pod 的任何实例。

这个问题确实很有趣,因为它突显了 Kubernetes 提供的安全功能如果配置错误也可能会带来危害。接下来,我们将讨论 kube-hunter,这是一个开源工具,用于查找集群中已知的安全问题。

使用 kube-hunter 扫描已知的漏洞

Kubernetes 发布的安全公告和公告(kubernetes.io/docs/reference/issues-security/security/)是跟踪 Kubernetes 中发现的新安全漏洞的最佳方式。这些公告和咨询电子邮件可能会有点压倒性,很可能会错过重要的漏洞。为了避免这些情况,定期检查集群中是否存在已知 CVE 的工具就派上用场了。kube-hunter是一个由 Aqua 开发和维护的开源工具,可帮助识别您的 Kubernetes 集群中已知的安全问题。

设置kube-hunter的步骤如下:

  1. 克隆存储库:
$git clone https://github.com/aquasecurity/kube-hunter
  1. 在您的集群中运行kube-hunter pod:
$ ./kubectl create -f job.yaml
  1. 查看日志以查找集群中的任何问题:
$ ./kubectl get pods
NAME                READY   STATUS              RESTARTS   AGE
kube-hunter-7hsfc   0/1     ContainerCreating   0          12s

以下输出显示了 Kubernetes v1.13.0 中已知的漏洞列表:

图 13.2 - kube-hunter 的结果

图 13.2 - kube-hunter 的结果

这个截图突出显示了kube-hunter在 Kubernetes v1.13.0 集群中发现的一些问题。kube-hunter发现的问题应该被视为关键,并应立即解决。

摘要

在本章中,我们讨论了 CVE 的重要性。这些公开已知的标识对于集群管理员、安全研究人员和攻击者都很重要。我们讨论了由 MITRE 维护的 CVE 条目的重要方面。然后我们看了四个知名的 CVE,并讨论了每个 CVE 的问题和缓解策略。作为集群管理员,升级kubectl客户端和 Kubernetes 版本应该始终是您的首要任务。然而,添加缓解策略以检测和防止由未公开报告的类似问题引起的利用同样重要。最后,我们讨论了一个开源工具kube-hunter,它可以定期识别您的 Kubernetes 集群中的问题。这消除了集群管理员密切关注 Kubernetes 的安全公告和公告的额外负担。

现在,您应该能够理解公开披露的漏洞的重要性,以及这些公告如何帮助加强您的 Kubernetes 集群的整体安全姿态。阅读这些公告将帮助您识别集群中的任何问题,并有助于加固您的集群。

问题

  1. CVE 条目对集群管理员、安全研究人员和攻击者来说最重要的部分是什么?

  2. 为什么客户端安全问题,如 CVE-2019-11246 对 Kubernetes 集群很重要?

  3. 为什么 kube-apiserver 中的 DoS 问题被视为高严重性问题?

  4. 比较 API 服务器中经过身份验证与未经身份验证的 DoS 问题。

  5. 讨论kube-hunter的重要性。

更多参考资料

第十四章:评估

第一章

  1. 扩展、运营成本和更长的发布周期。

  2. 主要组件运行在主节点上。这些组件负责管理工作节点。主要组件包括kube-apiserveretcdkube-schedulerkube-controller-managercloud-controller-managerdns-server

  3. Kubernetes 部署帮助根据标签和选择器扩展/缩小 Pod。部署封装了副本集和 Pod。部署的 YAML 规范包括 Pod 的实例数量和template,它与 Pod 规范相同。

  4. OpenShift、K3S 和 Minikube。

  5. Kubernetes 环境具有高度可配置性,并由众多组件组成。可配置性和复杂性与不安全的默认设置是一个令人担忧的原因。此外,集群中主要组件的 compromis 是引起违规的最简单方式。

第二章

  1. Pod。

  2. 网络命名空间和 IPC 命名空间。

  3. 用于保存其他容器的网络命名空间的占位符。

  4. ClusterIP、NodePort、LoadBalancer 和 ExternalName。

  5. Ingress 支持第 7 层路由,并且不需要来自云提供商的额外负载均衡器,而 LoadBalancer 服务需要每个服务一个负载均衡器。

第三章

  1. 威胁建模是一个迭代的过程,从设计阶段开始。

  2. 最终用户、内部攻击者和特权攻击者。

  3. 存储在etcd中的未加密数据。

  4. Kubernetes 环境的复杂性增加了在 Kubernetes 环境中使用威胁建模应用程序的难度。

  5. Kubernetes 引入了与应用程序的额外资产和交互。这增加了 Kubernetes 中应用程序的复杂性,增加了攻击面。

第四章

  1. Role对象包含由动词和资源组成的规则,指示命名空间中资源的操作特权。

  2. RoleBinding对象将命名空间中的Role对象与一组主体(例如UserServiceAccount)链接起来。它用于将 Role 对象中定义的特权授予主体。

  3. RoleBinding表示主体拥有的特权在RoleBinding对象的命名空间中有效。ClusterRoleBinding表示主体拥有的特权在整个集群中有效。

  4. hostPIDhostNetworkhostIPC

  5. 为具有出口规则的 Pod 创建网络策略。

第五章

  1. 主要组件、工作组件和 Kubernetes 对象。

  2. Pod、service/Ingress、api-server、节点和命名空间。

  3. RBAC 和网络策略。

  4. Pod 中的进程可以访问主机 PID 命名空间,查看工作节点上运行的所有进程。

kind: NetworkPolicy
metadata:
  name: allow-good
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          from: <allowed_label>

第六章

  1. 基于令牌的身份验证使静态令牌能够用于识别集群中请求的来源。静态令牌无法在不重新启动 API 服务器的情况下进行更新,因此不应使用。

  2. NodeRestriction准入控制器确保 kubelet 只能修改其正在运行的节点的节点和 Pod 对象。

  3. --encryption-provider-config传递给 API 服务器,以确保在etcd中对数据进行加密。

  4. dnsmasq中的安全漏洞,SkyDNS 中的性能问题,以及使用单个容器而不是三个容器来提供相同功能的kube-dns

  5. 您可以在 EKS 集群上使用kube-bench如下:

$ git clone : https://github.com/aquasecurity/kube-bench
$ kubectl apply -f job-eks.yaml

第七章

  1. 在生产集群中不应使用静态令牌和基本身份验证。这些模块使用静态凭据,需要重新启动 API 服务器才能更新。

  2. 集群管理员可以使用用户模拟特权来测试授予新用户的权限。使用kubectl,集群管理员可以使用--as --as-group标志以不同的用户身份运行请求。

  3. Kubernetes 中默认启用了 Node 和 RBAC。应该使用这些。如果集群使用远程 API 进行授权,则应改用 Webhook 模式。

  4. EventRateLimit准入控制器指定 API 服务器可以处理的请求的最大限制。另一方面,LimitRanger 确保 Kubernetes 对象遵守LimitRange对象指定的资源限制。

  5. 拒绝使用rego策略创建具有test.example端点的 Ingress 如下:

package kubernetes.admission
import data.kubernetes.namespaces
operations = {"CREATE", "UPDATE"}
deny[msg] {
    input.request.kind.kind == "Ingress"
    operations[input.request.operation]
    host := input.request.object.spec.rules[_].host
    host == "test.example"
    msg := sprintf("invalid ingress host %q", [host])
}

第八章

  1. 定义一个命令,要求 Docker 引擎定期检查容器的健康状态。

  2. COPY指令只能将文件从构建机器复制到镜像的文件系统,而ADD指令不仅可以从本地主机复制文件,还可以从远程 URL 检索文件到镜像的文件系统。使用ADD可能会引入从互联网添加恶意文件的风险。

  3. CAP_NET_BIND_SERVICE

  4. runAsNonRoot设置为true,kubelet 将阻止以 root 用户身份运行容器。

  5. 创建具有特权的角色,使用PodSecurityPolicy对象,并创建rolebinding对象将角色分配给工作负载使用的服务账户。

第九章

  1. Docker history <image name>.

  2. 7-8.9.

  3. anchore-cli image add <image name>.

  4. anchore-cli image vuln <image name> all.

  5. anchore-cli evaluate check <image digets> --tag <image full tag>.

  6. 它有助于识别具有最新已知漏洞的图像。

第十章

  1. 资源请求指定 Kubernetes 对象保证获得的资源,而限制指定 Kubernetes 对象可以使用的最大资源。

  2. 限制内存为 500 mi 的资源配额如下:

apiVersion: v1
kind: ResourceQuota
metadata:
    name: pods-medium
spec:
    hard:
      memory: 500Mi
  1. LimitRanger 是一个实施 LimitRanges 的准入控制器。LimitRange 定义了 Kubernetes 资源的约束。限制范围可以应用于 Pod、容器或persistantvolumeclaim。命名空间资源配额类似于LimitRange,但对整个命名空间进行强制执行。

  2. 服务账户令牌。

  3. Prometheus 和 Grafana。

第十一章

  1. 秘密数据将记录在 Kubernetes 审计日志中。

  2. --master-zones.

  3. 将更新的秘密同步到 Pod 的挂载卷。

  4. 系统调用和 Kubernetes 审计事件。

  5. proc.name.

  6. 检查运行中的容器,稍后可以在隔离环境中恢复。

  7. 故障排除和安全调查。

第十二章

  1. 仪表板在未启用身份验证的情况下使用。

  2. 不要运行仪表板,或者为仪表板启用身份验证。

  3. 不。这可能是加密挖矿攻击,但也可能是由其他原因引起的,比如应用程序错误。

  4. 加密挖矿二进制文件使用 HTTP 或 HTTPS 协议连接到挖矿池服务器,而不是 stratum。

  5. Kubernetes 集群的配置、构建、部署和运行时。

第十三章

  1. 集群管理员跟踪 CVE ID,以确保 Kubernetes 集群不容易受到已知的公开问题的影响。安全研究人员研究参考部分,以了解问题的技术细节,以开发 CVE 的缓解措施。最后,攻击者研究参考部分,以找到未修补的变体或使用类似技术来发现代码其他部分的问题。

  2. 客户端问题经常导致数据外泄或客户端上的代码执行。构建机器或集群管理员的机器通常包含敏感数据,对这些机器的攻击可能会对组织产生重大经济影响。

  3. api-server上的 DoS 问题可能导致整个集群的可用性中断。

  4. 未经身份验证的 DoS 问题比经过身份验证的 DoS 问题更严重。理想情况下,未经身份验证的用户不应该能够与api-server通信。如果未经身份验证的用户能够发送请求并导致api-server的 DoS 问题,那比经过身份验证的用户更糟糕。经过身份验证的 DoS 请求也非常严重,因为集群中的配置错误可能允许未经身份验证的用户提升权限并成为经过身份验证的用户。

  5. Kubernetes 的安全公告和通知是了解任何新公开已知漏洞的好方法。这些公告和通知相当嘈杂,管理员很容易忽略重要问题。定期运行kube-hunter有助于集群管理员识别管理员可能忽略的任何已知问题。

posted @ 2024-05-06 18:35  绝不原创的飞龙  阅读(20)  评论(0编辑  收藏  举报