大多数组织从答应开辟职员和管理员管理 Kubernetes 安装、配置和管理任何对象的集群开始。虽然这对于认识 Kubernetes 的团队来说是一种便利的方法,但并不安全,因为它大概会为攻击者打开闸门。一旦访问了集群,就可以执行任何恶意利用。
基于角色的访问控制(RBAC)将权限映射到用户或进程。考试要求深入了解涉及的 API 资源。领域“集群加固”还集中讨论保持集群版本最新以确保获取最新错误修复的主题。Kubernetes 通过端点公开 API 服务器。您应了解最小化其对外界暴露的策略。
系统加固
此领域的重点在于理解如何最小化对主机系统和外部网络的访问,以淘汰攻击面。这就是像 AppArmor 和 seccomp 这样的利用系统级工具发挥作用的地方。您需要展示它们的使用以满意要求。此领域还涉及在亚马逊云环境中运行的集群中使用 AWS IAM 角色的使用。
最小化微服务的漏洞
安全上下文定义了容器的特权和访问控制。平台和安全团队可以在组织级别上管理和执行所需的安全措施。考试要求您了解 Pod 安全策略和 OPA Gatekeeper 的目的。此外,您将被要求演示定义不同类型的 Secrets 并从 Pod 中使用它们以注入敏感的运行时信息。
偶然,您大概希望从未履历证的来源或埋伏不安全的来源实验容器镜像。像 gVisor 和 Kata Containers 这样的容器运行时沙箱可以在 Kubernetes 中配置,以使用非常受限的权限执行容器镜像。配置和使用这样的容器运行时沙箱是本事域的一部门。此外,您需要了解 mTLS Pod 对 Pod 加密的利益以及如何配置它。
供应链安全
在 Kubernetes 中,为了使微服务架构正常运行,Pod 需要能够在同一节点上或不同节点上运行的另一个 Pod 举行通讯,而不需要网络地址转换(NAT)。Kubernetes 在每个 Pod 创建时从其节点的 Pod CIDR 范围内为其分配唯一的 IP 地址。该 IP 地址是临时的,因此不能恒久稳固。每次 Pod 重新启动时,都会租用一个新的 IP 地址。发起使用 Pod 到服务的通讯而不是 Pod 到 Pod 的通讯,以便依赖于同等的网络接口。
分配给 Pod 的 IP 地址在所有节点和定名空间中是唯一的。这是通过在注册节点时为每个节点分配专用子网来实现的。在节点上创建新的 Pod 时,IP 地址是从分配的子网中租赁的。这由容器网络接口(CNI)插件处理。因此,节点上的 Pod 可以与集群中任何其他节点上运行的所有其他 Pod 举行通讯。
网络策略雷同于防火墙规则,但用于 Pod 到 Pod 的通讯。规则可以包括网络流量的方向(入站和/或出站),一个或多个定名空间内或跨不同定名空间的多个 Pod 的目标端口。关于网络策略底子的深入覆盖,请参阅册本 Certified Kubernetes Application Developer (CKAD) Study Guide(O’Reilly)或 Kubernetes 文档。CKS 考试重要偏重于使用网络策略限制集群级访问。
正确定义网络策略的规则大概具有挑战性。网站 networkpolicy.io 提供了一个网络策略的可视化编辑器,在浏览器中呈现图形表现。
场景:攻击者得到对 Pod 的访问权限
假设你在为一家运营 Kubernetes 集群的公司工作,该集群有三个工作节点。工作节点 1 当前作为微服务架构的一部门运行两个 Pod。考虑到 Kubernetes 对 Pod 到 Pod 网络通讯的默认举动,Pod 1 可以无限制地与 Pod 2 举行通讯,反之亦然。
如您在图 2-1 中所见,攻击者已经访问了 Pod 1. 假如没有定义网络策略,攻击者可以简朴地与 Pod 2 举行通讯,并造成额外的损害。这种漏洞不限于单个定名空间。Pod 3 和 Pod 4 也可以被访问和受到威胁。
https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/ckss_0201.png
图 2-1. 已经访问 Pod 1 的攻击者可以访问其他 Pod 的网络
networkpolicy.networking.k8s.io/default-deny-ingress created
复制代码
让我们看看这如何改变 Pod 到 Pod 网络通讯的运行时举动。frontend Pod 无法再与backend Pod 通讯,通过运行与之前相同的wget下令观察到这一点。网络调用在一秒后超时,由 CLI 选项--timeout定义:
$ kubectl exec frontend -it -n g04 -- /bin/sh
/ # wget --spider --timeout=1 10.0.0.43:3000
Connecting to 10.0.0.43:3000 (10.0.0.43:3000)
wget: download timed out
/ # exit
复制代码
此外,运行在不同定名空间中的 Pod 也无法再毗连到backend Pod。以下wget下令从运行在default定名空间中的other Pod 到backend Pod 的 IP 地址的调用:
$ kubectl exec other -it -- /bin/sh
/ # wget --spider --timeout=1 10.0.0.43:3000
Connecting to 10.0.0.43:3000 (10.0.0.43:3000)
wget: download timed out
复制代码
此调用也超时。
答应细粒度入站流量
网络策略是可加的。要为网络通讯授予更多权限,只需创建另一个具有更精细规则的网络策略。例如,我们想要仅答应来自同一定名空间中的frontend Pod 的入口流量访问backend Pod。独立于它们运行的定名空间,应拒绝所有其他 Pod 的入口流量。
网络策略大量使用标签选择来定义规则。识别g04定名空间中的标签及其运行在同一定名空间中的 Pod 对象,以便在网络策略中使用它们:
$ kubectl get ns g04 --show-labels
NAME STATUS AGE LABELS
g04 Active 12m app=orion,kubernetes.io/metadata.name=g04
$ kubectl get pods -n g04 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
backend 1/1 Running 0 9m46s tier=backend
frontend 1/1 Running 0 9m46s tier=frontend
复制代码
g04定名空间的标签分配包括键值对app=orion。backend Pod 的标签集包括键值对tier=backend,而frontend Pod 则包括键值对tier=frontend。
创建一个新的网络策略,答应frontend Pod 只在端口 3000 上与backend Pod 通讯。不答应其他任何通讯。在示例 2-3 中,YAML 清单显示了完整的网络策略定义。
示例 2-3. 答应入口流量的网络策略
[FAIL] 1.2.6 Ensure that the --kubelet-certificate-authority argument is set \
as appropriate (Automated) <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/4.png>
== Remediations master ==
...
1.2.1 Edit the API server pod specification file /etc/kubernetes/manifests/ \
kube-apiserver.yaml on the control plane node and set the below parameter.
--anonymous-auth=false
...
1.2.6 Follow the Kubernetes documentation and setup the TLS connection between <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
the apiserver and kubelets. Then, edit the API server pod specification file <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
/etc/kubernetes/manifests/kube-apiserver.yaml on the control plane node and \ <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
set the --kubelet-certificate-authority parameter to the path to the cert \ <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
file for the certificate authority. <https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/cks-stdgd/img/5.png>
$ kubectl get podsNAME READY STATUS RESTARTS AGEkube-bench-master-5gjdn 0/1 Completed 0 10s$ kubectl logs kube-bench-master-5gjdn | grep 1.2.12[PASS] 1.2.12 Ensure that the admission control plugin AlwaysPullImages is \set (Manual)
许多这些端口是可配置的。例如,您可以通过在配置文件 `/etc/kubernetes/manifests/kube-apiserver.yaml` 中使用 `--secure-port` 标志提供不同的值来修改 API 服务器端口,如在集群组件的 [文档](https://oreil.ly/TTzAz) 中所述。有关所有其他集群组件,请参阅它们各自的文档。
表 2-2 列出了工作节点上的默认入站端口。
表 2-2\. 入站工作节点端口
| 端口范围 | 用途 |
| --- | --- |
| 10250 | Kubelet API |
| 30000–32767 | NodePort 服务 |
要保护集群组件使用的端口,设置防火墙规则以减少攻击面。例如,您可以决定不将 API 服务器暴露给企业内部以外的任何人。只有在 VPN 登录时,使用 `kubectl` 的客户端才能对 Kubernetes 集群运行命令,从而使集群更不容易受到攻击。
云提供商 Kubernetes 集群(例如 AWS、Azure 或 Google Cloud)公开了所谓的元数据服务。元数据服务是可以提供敏感数据(如认证令牌)的 API,供 VM 或 Pod 在不需要额外授权的情况下消费。在 CKS 考试中,您需要了解这些节点端点和云提供商元数据服务。此外,您应该对如何防止未经授权访问它们有高层次的理解。
Kubernetes 仪表板作为集群内的一个 Pod 运行。安装仪表板还会创建一个 `ClusterIP` 类型的 Service,只允许从集群内部访问该端点。要使仪表板对最终用户可访问,必须将该 Service 暴露到集群外部。例如,可以切换到 `NodePort` Service 类型或者部署一个 Ingress。图 2-4 展示了部署和访问仪表板的高级架构。
默认情况下,Pod 之间的通信是无限制的。使用最小特权原则实例化默认拒绝规则以限制 Pod 之间的网络流量。网络策略的 `spec.podSelector` 属性根据标签选择选择目标 Pod 应用规则。入站和出站规则定义了允许进出流量的 Pod、命名空间、IP 地址和端口。网络策略可以进行聚合。默认拒绝规则可能禁止入站和/或出站流量。可以使用更精细的定义打开这些规则的其他网络策略。
API 服务器是访问 Kubernetes 集群的网关。任何人类用户、客户端(例如`kubectl`)、集群组件或服务账户都将通过 HTTPS 进行 RESTful API 调用访问 API 服务器。这是执行操作(如创建 Pod 或删除 Service)的*中心点*。
在本节中,我们将专注于与 API 服务器相关的安全特定方面。关于 API 服务器的内部工作方式以及 Kubernetes API 的使用详细讨论,请参考 Brendan Burns 和 Craig Tracey(O'Reilly)的书籍[*管理 Kubernetes*](https://learning.oreilly.com/library/view/managing-kubernetes/9781492033905)。
## 处理请求
图 3-1 说明了向 API 服务器发出调用时请求经历的阶段。有关更多信息,请参阅[Kubernetes 文档](https://oreil.ly/DuLdf)。
第二阶段确定了第一阶段提供的身份是否可以访问动词和 HTTP 路径请求。因此,第二阶段处理请求的*授权*,使用标准的 Kubernetes RBAC 模型实现。在这里,我们要确保服务账户被允许列出 Pods 或者根据请求创建新的 Service 对象。
请求处理的第三阶段涉及*准入控制*。准入控制验证请求是否格式正确,并在处理请求之前可能需要进行修改。例如,准入控制策略可以确保创建 Pod 的请求包含特定标签的定义。如果没有定义该标签,则请求将被拒绝。
最后一个阶段确保了请求中包含的资源是有效的。请求*验证*可以作为准入控制的一部分来实现,但不是必须的。例如,这个阶段确保了服务对象的名称遵循提供的 DNS 名称的标准 Kubernetes 命名规则。
## 连接到 API 服务器
运行以下命令很容易确定 API 服务器的端点:
复制代码
$ kubectl cluster-info
Kubernetes control plane is running at https://172.28.40.5:6443
…
对于给定的 Kubernetes 集群,API 服务器已通过 URL [*https://172.28.40.5:6443*](https://172.28.40.5:6443) 暴露。此外,您还可以查看 API 服务器配置文件中的命令行选项 `--advertise-address` 和 `--secure-port` 来确定端点。您可以在 `/etc/kubernetes/manifests/kube-apiserver.yaml` 找到 API 服务器配置文件。
# 配置 API 服务器的不安全端口
可以配置 API 服务器使用不安全端口(例如,80)已在 Kubernetes 1.10 版本中弃用。在版本 1.24 中,不安全端口标志 `--port` 和 `--insecure-port` 已完全删除,因此不能再用于配置 API 服务器。有关更多信息,请参阅[发布说明](https://oreil.ly/OTsmV)。
### 使用 Kubernetes 服务
Kubernetes 使得特定用例更方便访问 API 服务器。例如,您可能希望从 Pod 发送请求到 Kubernetes API。而不是使用 API 服务器的 IP 地址和端口,您可以简单地引用名为 `kubernetes.default.svc` 的服务。这个特殊的服务位于 `default` 命名空间中,并由集群自动启动。删除服务将自动重新创建它。您可以通过以下命令轻松找到该服务:
复制代码
$ kubectl get service kubernetes
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 443/TCP 32s
检查此服务的端点时,您将看到它指向 API 服务器的 IP 地址和端口,如通过执行以下命令所示:
复制代码
$ kubectl get endpoints kubernetes
NAME ENDPOINTS AGE
kubernetes 172.28.40.5:6443 4m3s
服务的 IP 地址和端口也通过环境变量暴露给 Pod。您可以从容器内运行的程序中读取环境变量的值。服务的 IP 地址由环境变量 `KUBERNETES_SERVICE_HOST` 反映。端口可以使用环境变量 `KUBERNETES_SERVICE_PORT` 访问。要渲染环境,请简单地使用 `env` 命令在临时 Pod 中访问环境变量:
如果您正在将 API 服务器暴露到互联网,请问您是否有必要这样做。一些云提供商提供创建私有集群的选项,这将限制或完全禁用对 API 服务器的公共访问。有关更多信息,请参阅[EKS](https://oreil.ly/W4Oma)和[GKE](https://oreil.ly/c7G-g)的文档页面。
如果您正在运营本地 Kubernetes 集群,您将需要实例化防火墙规则以阻止对 API 服务器的访问。设置防火墙规则超出了考试范围,因此本书不会讨论此内容。
## 场景:攻击者可以通过互联网调用 API 服务器
云服务提供商有时会将 API 服务器暴露在互联网上,以简化管理访问。攻击者可以尝试通过拒绝提供客户端证书或令牌来向 API 服务器端点发起匿名请求。如果攻击者幸运地捕获到用户凭据,那么可以执行经过身份验证的调用。根据分配给用户的权限,可以执行恶意操作。图 3-2 说明了一个攻击者从互联网调用 API 服务器的情况。
$ openssl req -new -key johndoe.key -out johndoe.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.’, the field will be left blank.
Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:johndoe
Email Address []:
Please enter the following ‘extra’ attributes
to be sent with your certificate request
A challenge password []:
$ kubectl config use-context johndoe
Switched to context “johndoe”.
使用 `kubectl` 作为客户端向 API 服务器发出调用,我们将验证这个操作是否被允许。用于在 `default` 命名空间中列出所有 Pod 的 API 调用已经被验证和授权:
复制代码
$ kubectl get pods
No resources found in default namespace.
命令的输出指示当前`default`命名空间在这个时间点上不包含任何 Pod 对象,但是调用是成功的。我们还将测试负面案例。列出命名空间对于用户来说是一个不允许的操作。执行相关的 `kubectl` 命令将返回一个错误消息:
复制代码
$ kubectl get namespaces
Error from server (Forbidden): namespaces is forbidden: User “johndoe” cannot
list resource “namespaces” in API group “” at the cluster scope
当你完成权限验证后,你可能想要切换回拥有管理员权限的上下文:
复制代码
$ kubectl config use-context minikube
Switched to context “minikube”.
## 情景:攻击者可以通过服务账号调用 API 服务器
用户代表经常使用 `kubectl` 可执行文件或 UI 仪表板与 Kubernetes 集群进行交互的真实人员。在某些罕见条件下,运行在 Pod 容器内的应用程序需要与 Kubernetes API 进行交互。这种需求的典型示例是包管理器[Helm](https://helm.sh)。Helm 根据捆绑在 Helm 图表中的 YAML 清单管理 Kubernetes 资源。Kubernetes 使用服务账户通过身份验证令牌将 Helm 服务进程与 API 服务器进行身份验证。可以将此服务账户分配给 Pod,并映射到 RBAC 规则。
$ kubectl logs list-objects -c deployments -n k97
{
“kind”: “Status”,
“apiVersion”: “v1”,
“metadata”: {},
“status”: “Failure”,
“message”: "deployments.apps is forbidden: User
“system:serviceaccount:k97:sa-api” cannot list resource
“deployments” in API group “apps” in the namespace
“k97"”,
“reason”: “Forbidden”,
“details”: {
“group”: “apps”,
“kind”: “deployments”
},
“code”: 403
}
随意修改 ClusterRole 对象,以允许列出 Deployment 对象。
### 禁用服务帐户令牌的自动装载
前一节中描述的 Pod 使用服务帐户的令牌作为针对 API 服务器进行身份验证的手段。将令牌文件挂载到 `/var/run/secrets/kubernetes.io/serviceaccount/token` 是每个服务帐户的标准行为。只有当 Pod 实际与 Kubernetes API 交互时,您才真正需要文件的内容。在所有其他情况下,此行为可能构成潜在的安全风险,因为访问 Pod 将直接将攻击者引导至令牌。
有多种用例表明希望创建一个禁用令牌自动挂载的服务账户。例如,您可能需要从外部工具或连续交付流水线访问 Kubernetes API,以查询现有对象的信息。在这些场景中,仍需要使用令牌对 API 服务器进行身份验证。所列场景不一定运行分配了服务账户的 Pod,而只是从诸如`curl`之类的工具执行 RESTful API 调用。
本章演示了与 Kubernetes API 交互的不同方式。我们通过切换到用户上下文执行 API 请求,并借助使用`curl`进行 RESTful API 调用。您需要了解如何确定 API 服务器的端点以及如何使用不同的身份验证方法,例如客户端凭据和 Bearer 令牌。探索 Kubernetes API 及其端点,以便获得更广泛的暴露。
理解为用户和服务账户定义 RBAC 规则的影响。
匿名用户对 Kubernetes API 的请求不会允许任何实质性操作。对于来自用户或服务账户的请求,您需要仔细分析授予主体的权限。通过创建相关对象来控制权限,了解定义 RBAC 规则的方方面面。服务账户在 Pod 中使用时会自动挂载令牌。如果打算从 Pod 中进行 API 调用,则仅暴露令牌作为卷。
1. 在名称空间`t23`中创建一个名为`service-list`的 Pod。该容器使用`alpine/curl:3.14`镜像,并在无限循环中向 Kubernetes API 发出一个列出`default`名称空间中 Service 对象的`curl`调用。创建并附加服务账户`api-call`。在 Pod 启动后检查容器日志。您期望从`curl`命令中看到什么响应?
1. 为服务账号分配 ClusterRole 和 RoleBinding,仅允许 Pod 所需的操作。查看 `curl` 命令的响应。
1. 配置 Pod 以禁用自动挂载服务账号令牌。检索令牌值并直接在 `curl` 命令中使用。确保 `curl` 命令仍然能够授权操作。
$ sudo apt purge --auto-remove snapd
Reading package lists… Done
Building dependency tree
Reading state information… Done
The following packages will be REMOVED:
snapd* squashfs-tools*
0 upgraded, 0 newly installed, 2 to remove and 116 not upgraded.
After this operation, 147 MB disk space will be freed.
Do you want to continue? [Y/n] y
…
$ sudo adduser ben
Adding user ‘ben’ …
Adding new group ‘ben’ (1001) …
Adding new user ‘ben’ (1001) with group ‘ben’ …
Creating home directory ‘/home/ben’ …
Copying files from ‘/etc/skel’ …
New password:
Retype new password:
…
$ sudo aa-status
apparmor module is loaded.
31 profiles are loaded.
31 profiles are in enforce mode.
/snap/snapd/15177/usr/lib/snapd/snap-confine
…
0 profiles are in complain mode.
14 processes have profiles defined.
14 processes are in enforce mode.
/pause (11934) docker-default
…
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.