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 中打开演示: [![在 Cloud Shell 中打开](http://gstatic.com/cloudssh/images/open-btn.svg)](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 /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 :` | | 检查节点上挂载的 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 官方支持的产品**。
标签:CISA项目, DevSecOps, GCP, GitHub Advanced Security, GKE, Google Kubernetes Engine, K8s 渗透测试, Metadata Concealment, Pandas, PodSecurityPolicy, Pod 安全, RCE, SSRF, 上游代理, 元数据安全, 协议分析, 子域名突变, 安全加固, 安全实验, 容器逃逸, 应用安全, 权限提升, 漏洞复现, 编程工具, 请求拦截, 远程代码执行