GoogleCloudPlatform/gke-secure-defaults-demo
GitHub: GoogleCloudPlatform/gke-secure-defaults-demo
这是一个演示 GKE 默认配置安全隐患及加固措施的实验室项目,帮助用户理解并防范 Pod 逃逸和集群权限提升风险。
Stars: 17 | Forks: 6
# Google Kubernetes Engine 安全默认配置演示
## 目录
* [Google Kubernetes Engine 安全默认配置演示](#google-kubernetes-engine-secure-defaults-demo)
* [简介](#introduction)
* [目标](#objectives)
* [前提条件](#prerequisites)
* [在 Google Cloud Shell 中运行演示](#run-demo-in-a-google-cloud-shell)
* [支持的操作系统](#supported-operating-systems)
* [工具](#tools)
* [版本](#versions)
* [部署步骤](#deployment-steps)
* [验证](#validation)
* [清理](#tear-down)
* [故障排除](#troubleshooting)
* [相关资料](#relevant-materials)
## 简介
本实验演示了默认 [Kubernetes Engine](https://cloud.google.com/kubernetes-engine/) 集群配置的一些安全问题,以及相应的强化措施,以防止多种 Pod 逃逸和集群权限提升路径。这些攻击路径适用于以下场景:
1. 面向外部的 Pod 中存在应用程序缺陷,允许服务器端请求伪造 (SSRF) 攻击。
2. Pod 内的容器被完全攻陷,导致远程命令执行 (RCE)。
3. 恶意内部用户或拥有受损内部用户凭证的攻击者,能够在特定 Namespace 中创建/更新 Pod。
以下安全设置将在禁用和启用状态下分别进行测试,以展示这些配置在现实世界中的影响:
* 禁用 [旧版 GCE Metadata API 端点][8]
* 启用 [元数据隐藏][10]
* 启用并配置 [PodSecurityPolicy][5]
## 目标
完成本实验后,您将了解保护 GKE 实例元数据的必要性,以及为您的环境定义适当的 PodSecurityPolicy 策略。
您将:
1. 在现有的 GCP 项目中创建一个小型 GKE 集群。
2. 从恶意内部用户的角度验证最常见的 Pod 逃逸和集群权限提升路径。
3. 通过挂载具有改进安全设置的新节点池来强化 GKE 集群以解决这些问题。
4. 验证集群不再允许发生这些操作。
## 前提条件
* 拥有启用了 Kubernetes Engine 服务的现有 Google Cloud 项目的访问权限。如果您没有 Google Cloud 帐户,请在[此处][2]注册免费试用。
* 此演示需要一个 Google Cloud 帐户和项目。该项目必须拥有适当的配额以运行至少具有 3 个 vCPU 和 10GB 内存的 Kubernetes Engine 集群。如何检查您帐户的配额记录在此处:[配额][1]。
### 支持的操作系统
此演示可以从 MacOS、Linux 运行,或者直接从 [Google Cloud Shell](https://cloud.google.com/shell/docs/) 运行。后一种选项最简单,因为它只需要浏览器访问 GCP,不需要额外的软件。两种替代方案的说明可以在下面找到。
### 从 Google Cloud Shell 部署演示
_注意:如果是在不使用 Cloud Shell 的情况下执行云部署,例如从本地机器或 GCP 外部的服务器,则可以跳过本节。_
[Google Cloud Shell](https://cloud.google.com/shell/docs/) 是 Google 提供的基于浏览器的终端,用于与您的 GCP 资源进行交互。它由一个免费的 Compute Engine 实例支持,预装了许多有用的工具,包括运行此演示所需的一切。
单击下面的按钮在您的 Cloud Shell 中打开演示:
[](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fgke-secure-defaults-demo&page=editor&tutorial=README.md)
要准备在 Cloud Shell 中使用 [gcloud](https://cloud.google.com/sdk/gcloud/),请在您刚打开的浏览器窗口底部的终端中执行以下命令:
```
gcloud init
```
响应提示并继续执行以下部署说明。提示将包括您要以其身份运行的帐户、当前项目,以及可选的默认区域和地区。这些配置 Cloud Shell 本身——演示使用的实际项目、区域和地区将在下面单独配置。
### 在不使用 Cloud Shell 的情况下部署演示
_注意:如果如上所述通过 Cloud Shell 部署演示,则可以跳过本节。_
对于不使用 Cloud Shell 的部署,您需要访问一台提供安装了以下工具的 [bash](https://www.gnu.org/software/bash/) shell 的计算机:
* [Google Cloud SDK (v214.0.0 或更高版本)](https://cloud.google.com/sdk/downloads)
* [kubectl (v1.12.0 或更高版本)](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
* [git](https://git-scm.com/)
使用 `git` 将此项目克隆到您的本地机器:
```
git clone https://github.com/GoogleCloudPlatform/gke-secure-defaults-demo
```
下载完成后,将当前工作目录更改为新项目:
```
cd gke-secure-defaults-demo
```
继续执行以下说明,从此目录运行所有命令。
## 部署步骤
_注意:以下说明适用于使用和不使用 Cloud Shell 执行的部署。_
要部署集群,请执行以下命令:
```
./create.sh -c default-cluster
```
将文本 `default-cluster` 替换为您要创建的集群的名称。
创建脚本完成时将输出以下消息:
```
...snip...
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
default-cluster us-central1-a 1.12.8-gke.6 34.66.214.195 n1-standard-1 1.12.8-gke.6 2 RUNNING
Fetching cluster endpoint and auth data.
kubeconfig entry generated for default-cluster.
```
该脚本将:
1. 在您的项目中启用必要的 API。具体是 `compute` 和 `container`。
2. 在您当前的 ZONE、VPC 和网络中创建一个新的 Kubernetes Engine 集群,该集群省略了配置 [GKE 元数据隐藏代理][10] 并且未启用阻止访问 [旧版 Compute Metadata API][8] 的设置。
3. 检索您的集群凭证以启用 `kubectl` 使用。
集群创建成功后,使用 `kubectl version` 命令检查您安装的 Kubernetes 版本:
```
kubectl version
Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.3", GitCommit:"5e53fd6bc17c0dec8434817e69b04a25d8ae0ff0", GitTreeState:"clean", BuildDate:"2019-06-06T01:44:30Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"12+", GitVersion:"v1.12.8-gke.10", GitCommit:"f53039cc1e5295eed20969a4f10fb6ad99461e37", GitTreeState:"clean", BuildDate:"2019-06-19T20:48:40Z", GoVersion:"go1.10.8b4", Compiler:"gc", Platform:"linux/amd64"}
```
您的 `kubectl` 版本(客户端)应与创建的 GKE 集群(服务器)相差不超过两个次要版本。
### 运行 Google Cloud-SDK Pod
从您的 Cloud Shell 提示符处,启动 Google Cloud-SDK 容器的单个实例,该实例将在退出 shell 后自动移除:
```
kubectl run -it --generator=run-pod/v1 --rm gcloud --image=google/cloud-sdk:latest --restart=Never -- bash
```
这需要一些时间才能完成。
您现在应该在 Pod 的容器内拥有一个 bash shell:
```
root@gcloud:/#
```
容器启动并显示命令提示符可能需要几秒钟。如果您没有看到命令提示符,请尝试按 __Enter__。
### 探索旧版 Compute Metadata 端点
在使用 1.11 或更低版本创建的 GKE 集群中,默认情况下可以使用“旧版”或 `v1beta1` Compute Metadata 端点。与当前的 Compute Metadata 版本 `v1` 不同,`v1beta1` Compute Metadata 端点不需要在所有请求中包含自定义 HTTP 头。在 1.12 或更高版本创建的新 GKE 集群上,旧版 Compute Engine 元数据端点现在默认已禁用。有关更多信息,请参阅:[保护集群元数据][10]
运行以下命令以访问“旧版” Compute Metadata 端点,无需自定义 HTTP 头即可获取运行此 Pod 的 GCE 实例名称:
```
curl -s http://metadata.google.internal/computeMetadata/v1beta1/instance/name && echo
gke-default-cluster-default-pool-b57a043a-6z5v
```
`&& echo` 命令有助于终端格式化和输出可读性。现在,重新运行相同的命令,但改用 `v1` Compute Metadata 端点:
```
curl -s http://metadata.google.internal/computeMetadata/v1/instance/name && echo
...snip...
Your client does not have permission to get URL :` |
| 检查节点上挂载的 Kubernetes secrets | `mount \| grep volumes \| awk '{print $3}' \| xargs ls` |
| `exec` 进入任何正在运行的容器(甚至进入另一个 Namespace 中的另一个 Pod) | `docker exec -it sh` |
`root` 用户可以执行的几乎每个操作都可用于此 Pod shell。这包括持久性机制,如添加 SSH 用户/密钥、在 Kubernetes 视图之外的主机上运行特权 docker 容器等等。
要退出 Pod shell,运行两次 `exit` —— 一次离开 `chroot`,另一次离开 Pod 的 shell:
```
exit
```
```
exit
```
现在您可以删除 `hostpath` Pod:
```
kubectl delete -f manifests/hostpath.yml
pod "hostpath" deleted
```
### 了解可用的控制措施
此演示的后续步骤将涵盖:
* __禁用旧版 GCE Metadata API 端点__ - 通过指定自定义元数据键和值,`v1beta1` 元数据端点将不再可从实例访问。
* __启用元数据隐藏__ - 在集群和/或节点池创建期间传递额外配置,每个节点上将安装一个轻量级代理,代理所有对 Metadata API 的请求并阻止访问敏感端点。
* __启用并配置 PodSecurityPolicy__ - 在 GKE 集群上配置此选项将添加 PodSecurityPolicy 准入控制器,该控制器可用于限制在 Pod 创建期间使用不安全的设置。在此演示的案例中,防止容器以 root 用户身份运行并拥有挂载底层主机文件系统的能力。
### 部署第二个节点池
为了让您能够在启用和未启用 Metadata 端点保护的情况下进行试验,您将创建包含两个额外设置的第二个节点池。调度到通用节点池的 将没有保护,而调度到第二个节点池的 Pod 将启用保护。
注意:在 GKE 1.12 及更高版本中,`--metadata=disable-legacy-endpoints=true` 设置将自动启用。下一个命令为清晰起见明确定义了它。
创建第二个节点池:
```
./second-pool.sh -c default-cluster
NAME MACHINE_TYPE DISK_SIZE_GB NODE_VERSION
second-pool n1-standard-1 100 1.12.8-gke.6
```
### 运行 Google Cloud-SDK Pod
在 Cloud Shell 中,启动 Google Cloud-SDK 容器的单个实例,该实例将仅在启用了保护的第二个节点池上运行,并且不以 root 用户身份运行。
```
kubectl run -it --generator=run-pod/v1 --rm gcloud --image=google/cloud-sdk:latest --restart=Never --overrides='{ "apiVersion": "v1", "spec": { "securityContext": { "runAsUser": 65534, "fsGroup": 65534 }, "nodeSelector": { "cloud.google.com/gke-nodepool": "second-pool" } } }' -- bash
```
您现在应该在名为 `second-pool` 的节点池上运行的 Pod 容器内拥有一个 bash shell。您应该看到以下内容:
```
nobody@gcloud:/$
```
容器启动并显示命令提示符可能需要几秒钟。
如果您没有看到命令提示符,请尝试按 __Enter__。
### 探索各种被阻止的端点
随着第二个节点池的配置设置为 `--metadata=disable-legacy-endpoints=true`,以下命令现在将按预期失败:
```
curl -s http://metadata.google.internal/computeMetadata/v1beta1/instance/name
...snip...
Legacy metadata endpoints are disabled. Please use the /v1/ endpoint.
...snip...
```
随着第二个节点池的配置设置为 `--workload-metadata-from-node=SECURE`,以下用于检索敏感文件 `kube-env` 的命令现在将失败:
```
curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/attributes/kube-env
This metadata endpoint is concealed.
```
但是,如果传递了正确的 HTTP 头,对非敏感端点的其他命令仍然会成功:
```
curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/name && echo
gke-default-cluster-second-pool-8fbd68c5-gzzp
```
退出 Pod:
```
exit
```
您现在应该回到了您的 shell。
### 部署 PodSecurityPolicy 对象
为了拥有继续操作所需的权限,请向您自己的用户帐户授予显式权限以成为 `cluster-admin:`
```
kubectl create clusterrolebinding clusteradmin --clusterrole=cluster-admin --user="$(gcloud config list account --format 'value(core.account)')"
clusterrolebinding.rbac.authorization.k8s.io/clusteradmin created
```
接下来,在默认 Namespace 中为所有经过身份验证的用户部署一个更具限制性的 `PodSecurityPolicy`:
```
kubectl apply -f manifests/restrictive-psp.yml
podsecuritypolicy.extensions/restrictive-psp created
```
接下来,添加提供“使用”此 PodSecurityPolicy 所需能力的 `ClusterRole`。
```
kubectl apply -f manifests/restrictive-psp-clusterrole.yml
clusterrole.rbac.authorization.k8s.io/restrictive-psp created
```
最后,在默认 Namespace 中创建一个 RoleBinding,允许任何经过身份验证的用户有权利用 PodSecurityPolicy。
```
kubectl apply -f manifests/restrictive-psp-clusterrolebinding.yml
rolebinding.rbac.authorization.k8s.io/restrictive-psp created
```
__注意:__ 在实际环境中,考虑将 ClusterRoleBinding 或 Namespace RoleBinding 中的 `system:authenticated` 用户替换为您希望有权在默认 Namespace 中创建 Pod 的特定用户或 Service Account。
### 启用 PodSecurityPolicy
接下来,启用 PodSecurityPolicy 准入控制器:
```
./enable-psp.sh -c default-cluster
```
这将需要几分钟才能完成。
### 部署被阻止的挂载主机文件系统的 Pod
由于用于部署 GKE 集群的帐户在上一步中被授予了 cluster-admin 权限,因此有必要创建另一个单独的“用户”帐户来与集群交互并验证 PodSecurityPolicy 的执行。为此,请运行:
```
./create-demo-developer.sh -c default-cluster
Created service account [demo-developer].
...snip...
Fetching cluster endpoint and auth data.
kubeconfig entry generated for default-cluster.
```
`create-demo-developer.sh` 脚本将创建一个名为 `demo-developer` 的新 Service Account,授予该 Service Account `container.developer` IAM 角色,创建一个 Service Account 密钥,配置 gcloud 以使用该 Service Account 密钥,然后配置 kubectl 在与集群通信时使用这些 Service Account 凭证。
现在,尝试创建另一个将底层主机文件系统 `/` 挂载到容器内名为 `/rootfs` 的文件夹的 Pod:
```
kubectl apply -f manifests/hostpath.yml
```
此输出验证它已被 PSP 阻止:
```
Error from server (Forbidden): error when creating "STDIN": pods "hostpath" is forbidden: unable to validate against any pod security policy: [spec.volumes[0]: Invalid value: "hostPath": hostPath volumes are not allowed to be used]
```
部署另一个符合 `restrictive-psp` 标准的 Pod:
```
kubectl apply -f manifests/nohostpath.yml
pod/nohostpath created
```
要查看添加到 Pod 的注释,该注释指示哪个 PodSecurityPolicy 授权了创建,请运行:
```
kubectl get pod nohostpath -o=jsonpath="{ .metadata.annotations.kubernetes\.io/psp }" && echo
restrictive-psp
```
恭喜!在此实验中,您在 Google Kubernetes Engine 中配置了一个默认 Kubernetes 集群。然后,您探测并利用了您的 Pod 可用的访问权限,强化了集群,并验证了这些恶意操作不再可能发生。
## 验证
以下脚本将验证演示是否正确部署:
```
./validate.sh -c default-cluster
```
将文本 `default-cluster` 替换为您要验证的集群的名称。如果脚本失败,它将输出:
```
Fetching cluster endpoint and auth data.
kubeconfig entry generated for default-cluster.
```
## 清理
以您的用户帐户重新登录。
```
gcloud auth login
```
以下脚本将销毁 Kubernetes Engine 集群。
```
./delete.sh -c default-cluster
Fetching cluster endpoint and auth data.
kubeconfig entry generated for default-cluster.
Deleting cluster
Deleting cluster default-cluster...
...snip...
deleted service account [demo-developer@my-project-id.iam.gserviceaccount.com]
```
将文本 `default-cluster` 替换为您要删除的集群的名称。
## 故障排除
### 关于项目配额的错误
如果您收到关于配额的错误,请增加您项目中的配额。有关更多详细信息,请参见[此处][1]。
## 相关资料
1. [Google Cloud 配额][1]
2. [注册 Google Cloud][2]
3. [Google Cloud Shell][3]
4. [强化您的集群][4]
5. [PodSecurityPolicy][5]
6. [限制 Pod 权限][6]
7. [节点 Service Account][7]
8. [保护节点元数据][8]
9. [发布阶段][9]
10. [保护集群元数据][10]
注意,**这不是 Google 官方支持的产品**。
/computeMetadata/v1/instance/name from this server. Missing Metadata-Flavor:Google header.
...snip...
```
请注意它是如何返回错误的,指出它需要存在自定义 HTTP 头。在下一次运行中添加自定义头并检索运行此 Pod 的 GCE 实例名称:
```
curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/name && echo
gke-default-cluster-default-pool-b57a043a-6z5v
```
如果在访问 GCE 实例元数据端点时不需要自定义 HTTP 头,应用程序中允许攻击者欺骗代码检索攻击者指定的 Web URL 内容的缺陷可能会为枚举和潜在的凭证泄露提供一种简单的方法。通过要求自定义 HTTP 头,攻击者需要利用应用程序缺陷,使他们能够控制 URL 并添加自定义头,才能成功执行此攻击。
保留此 Pod 内的 shell 以备下一步使用。如果您意外从 Pod 退出,只需重新运行:
```
kubectl run -it --generator=run-pod/v1 --rm gcloud --image=google/cloud-sdk:latest --restart=Never -- bash
```
### 探索 GKE 节点引导凭证
在同一个 Pod shell 内,运行以下命令列出与底层 GCE 实例关联的属性。请务必包含尾部斜杠:
```
curl -s http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/
```
此列表中最敏感的数据可能是 `kube-env`。它包含几个变量,`kubelet` 在将节点附加到 GKE 集群时将其用作初始凭证。变量 `CA_CERT`、`KUBELET_CERT` 和 `KUBELET_KEY` 包含此信息,因此对非集群管理员被视为敏感信息。
要查看可能敏感的变量和数据,请运行以下命令:
```
curl -s http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/kube-env
```
因此,在以下任何情况下:
1. Pod 应用程序中存在允许 SSRF 的缺陷
2. Pod 中存在允许 RCE 的应用程序或库缺陷
3. 有能力创建或 exec 进入 Pod 的内部用户
存在通过 Compute Metadata 端点泄露和窃取敏感 `kubelet` 引导凭证的高风险。使用 `kubelet` 凭证,在某些情况下可以利用它们将权限提升到 `cluster-admin`,从而完全控制 GKE 集群,包括所有数据、应用程序以及对底层节点的访问。
### 利用分配给此节点池 Service Account 的权限
默认情况下,启用了 Compute API 的 GCP 项目在项目中拥有格式为 `NNNNNNNNNN-compute@developer.gserviceaccount.com` 的默认 Service Account,并附加了 `Editor` 角色。同样默认情况下,创建时未指定 Service Account 的 GKE 集群将使用默认的 Compute Service Account 并将其附加到所有工作节点。
运行以下 `curl` 命令列出与附加到底层 GCE 实例的 Service Account 关联的 OAuth 作用域:
```
curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes
https://www.googleapis.com/auth/devstorage.read_only
https://www.googleapis.com/auth/logging.write
https://www.googleapis.com/auth/monitoring
https://www.googleapis.com/auth/service.management.readonly
https://www.googleapis.com/auth/servicecontrol
https://www.googleapis.com/auth/trace.append
```
身份验证作用域和 Service Account 权限的组合决定了此节点上的应用程序可以访问的内容。上述列表是大多数 GKE 集群所需的最小作用域,但某些用例需要增加作用域。
如果在集群创建期间将身份验证作用域配置为包含 `https://www.googleapis.com/auth/cloud-platform`,这将允许任何 GCP API 被视为“在作用域内”,并且只有分配给 Service Account 的 IAM 权限才能决定可以访问的内容。如果使用的是默认 Service Account 且未修改默认 IAM 角色 `Editor`,这实际上意味着此节点池上的任何 Pod 都对部署 GKE 集群的 GCP 项目拥有 `Editor` 权限。由于 `Editor` IAM 角色具有广泛的读/写权限来与项目中的资源(如 Compute 实例、GCS 存储桶、GCR 注册表等)进行交互,这很可能是不希望看到的。
通过键入以下命令退出此 Pod:
```
exit
```
### 部署挂载主机文件系统的 Pod
“逃逸”到底层主机的最简单路径之一是使用标准 Kubernetes `volumes` 和 `volumeMounts` 在 `Pod` 规范中将主机的文件系统挂载到 Pod 的文件系统中。
为了演示这一点,运行以下命令创建一个 Pod,该 Pod 将底层主机文件系统 `/` 挂载到容器内名为 `/rootfs` 的文件夹:
```
kubectl apply -f manifests/hostpath.yml
```
运行 `kubectl get pod` 并重新运行直到它处于 "Running" 状态:
```
kubectl get pod
NAME READY STATUS RESTARTS AGE
hostpath 1/1 Running 0 30s
```
### 探索并攻陷底层主机
运行以下命令在您刚刚创建的 Pod 中获取 shell:
```
kubectl exec -it hostpath -- bash
```
将 Pod shell 的根文件系统指向底层主机的根文件系统:
```
chroot /rootfs /bin/bash
hostpath / #
```
通过这些简单的命令,该 Pod 现在实际上是节点上的一个 `root` shell。您现在可以执行以下操作:
| 以完全权限运行标准 docker 命令 | `docker ps` |
|--------------------------------------------------------------------------------|------------------------------------------------------|
| 列出所有本地 docker 镜像 | `docker images` |
| `docker run` 您选择的特权容器 | `docker run --privileged 标签:CISA项目, DevSecOps, GCP, GitHub Advanced Security, GKE, Google Kubernetes Engine, K8s 渗透测试, Metadata Concealment, Pandas, PodSecurityPolicy, Pod 安全, RCE, SSRF, 上游代理, 元数据安全, 协议分析, 子域名突变, 安全加固, 安全实验, 容器逃逸, 应用安全, 权限提升, 漏洞复现, 编程工具, 请求拦截, 远程代码执行