LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

CKS 模拟考 A

在 LabHub 中继续学习

目标

在与真实 CKS 相同的条件下,于 120 分钟内完成 17 道任务。及格线为 67%,并采用部分计分制,因此通过 17 道题中的 12 道即视为完成。

这是模拟考试。请先不看提示和答案,独立完成全部题目。 遇到卡住的题目,最好先做标记并跳过,等剩余时间再回来处理。 可以随时评分,多次评分也不会改变结果。

为什么重要

CKS 是唯一要求先通过 CKA 的考试。它假定你已经具备操作 Kubernetes 的能力,因此题目与其说是“请创建什么”,不如说更接近“找出这个配置中的风险并修复”。考试考查防御和诊断,不涉及攻击技术。

实际工作也是如此。集群遭到入侵的入口通常不是新功能,而是沿用默认值的地方:命名空间没有启用 Pod 安全准入,默认服务账户令牌挂载到所有 Pod,镜像又以可变标签部署。本考试考查的正是这份清单。

考试环境(真实考试中确认的事实)

本模拟考试环境的差异

本实验的集群是在 Pod 内运行的单人集群。kube-apiserver 是真实的,因此清单、RBAC、Pod 安全准入和准入策略会**真实运行并实际拒绝请求。**但由于没有真正运行容器的运行时,无法验证以下两项:

其余项目都会在活动集群中重新计算并评分。权限通过 kubectl auth can-i 检查,准入策略通过服务器 dry-run 重新查询当时的判定。

步骤

集群设置

  1. 创建命名空间 prod,并为其中所有 Pod 创建同时默认拒绝入站和出站流量的 NetworkPolicy default-deny。不要添加任何允许规则。
  2. 在命名空间 prod 中,阻止带有 app=web 标签的 Pod 访问节点元数据端点 169.254.169.254/32,但保持其他出站流量开放,并将该 NetworkPolicy 命名为 deny-metadata
  3. 使用 CN=shop.internal 的自签名证书在命名空间 prod 中创建 TLS Secret web-tls,并为主机 shop.internal 创建使用该 Secret 终止 TLS 的 Ingress web。后端是 Service web 的 80 端口。

集群加固

  1. 在命名空间 prod 中创建 ServiceAccount report-runner,使该账户只能在 prod 中对 Pod 执行 getlistwatch,并为此创建 Role pod-reader 与 RoleBinding report-runner-pod-reader。不要授予其他权限。
  2. 禁止命名空间 proddefault ServiceAccount 自动挂载令牌。然后在 prod 中创建 Pod frontend,使用 ServiceAccount report-runner,并在 Pod 规约中也关闭令牌自动挂载。
  3. 使安全审计组 security-audit 只能在整个集群中读取 Pod、命名空间和 NetworkPolicy,并为此创建 ClusterRole security-auditor 和 ClusterRoleBinding security-auditor。不要授予写权限或 Secret 访问权限。

系统加固

  1. /root/exam/seccomp/audit.json 中编写 seccomp 配置文件。默认动作是 SCMP_ACT_ERRNO,并且至少要用 SCMP_ACT_ALLOW 放行若干系统调用。然后在命名空间 prod 中创建 Pod probe,以 Localhost 方式从路径 profiles/audit.json 使用该配置文件。
  2. /root/exam/apparmor/k8s-deny-write 中编写 AppArmor 配置文件。配置文件名为 k8s-deny-write,必须包含 #include <tunables/global> 行以及阻止所有写入的 deny /** w, 规则。然后在命名空间 prod 中创建 Deployment logshipper,使 Pod 模板的 securityContext.appArmorProfileLocalhost 方式使用该配置文件。

减少微服务漏洞

  1. 创建命名空间 payments,将 Pod 安全准入的 enforceauditwarn 三种模式全部设为 restricted,并将三种模式的版本固定为 latest
  2. 尝试在命名空间 payments 中应用违反 restricted 的 Pod bad-pod,将包括标准错误在内的拒绝响应保存到 /root/exam/denied.txt。Pod 不得实际创建。
  3. 创建 RuntimeClass gvisor(handler 为 runsc),并在命名空间 payments 中创建副本数为 2 的 Deployment checkout。Pod 模板使用该 RuntimeClass,且必须通过 restricted。容器名为 app;在 Pod 级别设置 runAsNonRoot: trueseccompProfile.type: RuntimeDefault,在容器级别设置 allowPrivilegeEscalation: falsecapabilities.drop: [ALL]readOnlyRootFilesystem: true

供应链安全

  1. 创建命名空间 supply 和 Deployment payments-api。容器名为 api,镜像必须固定到摘要而不是标签。值为 registry.internal/payments-api@sha256:140eab0459241fb1643767dd4cc3576d282b0059a6328521e714cb545fa4adeaimagePullPolicyIfNotPresent
  2. /root/exam/Dockerfile 中编写构建部署镜像的 Dockerfile。它必须是分离构建阶段与运行阶段的多阶段构建,将所有 FROM 基础镜像固定到 @sha256: 摘要,并且只能通过 COPY --from= 获取产物。不要使用 ADD;最后的 USER 必须是非 0 的数字 UID;不要在 ENVARG 中留下疑似秘密的名称。
  3. 阻止使用未经批准的注册表镜像。创建 ValidatingAdmissionPolicy trusted-images 和 ValidatingAdmissionPolicyBinding trusted-images,要求所有容器镜像都以 registry.internal/ 开头。绑定的 validationActionsDeny,作用范围仅限命名空间 supply,不得影响其他命名空间。

监控、日志与运行时安全

  1. /root/exam/audit/policy.yaml 中编写审计策略。apiVersionaudit.k8s.io/v1kindPolicy,顶层 omitStages 只有 RequestReceived。规则恰好三条,顺序很重要。第一,记录核心组的 secretsconfigmaps,级别为 Metadata。第二,记录 pods/execpods/attachpods/portforward,级别为 RequestResponse。第三,用不指定对象的 Metadata 规则接收其余全部请求。
  2. /root/exam/audit/kube-apiserver.yaml 中编写启用审计日志的 kube-apiserver 静态 Pod 清单。第一个容器名为 kube-apiserver,并包含以下五个标志: --audit-policy-file=/etc/kubernetes/audit/policy.yaml--audit-log-path=/var/log/kubernetes/audit/audit.log--audit-log-maxage=30--audit-log-maxbackup=10--audit-log-maxsize=100。此外分别挂载 hostPath 卷 audit-policyaudit-logs;策略挂载必须为 readOnly: true,日志挂载不得为只读。
  3. 在命名空间 prod 中创建 Deployment ledger。容器名为 app,并设置 readOnlyRootFilesystem: trueallowPrivilegeEscalation: falsecapabilities.drop: [ALL]。需要写入的 /tmpemptyDirscratch 提供;不要使用 hostPath 卷以及 hostNetworkhostPIDhostIPC

参考

prod 命名空间默认拒绝

将 NetworkPolicy 的 podSelector 设为空对象,会选中该命名空间中的所有 Pod。还必须在 policyTypes 中同时写入 Ingress 和 Egress,才能阻断双向流量。完全不写规则(ingress/egress)表示“没有要允许的流量”。

阻断节点元数据访问

ipBlock 使用 cidr 开放范围,再用 except 排除其中一部分。若要允许全部地址而只阻断一个地址,cidr 应为 0.0.0.0/0,并在 except 中写入该地址。本题只处理 Egress。

TLS 终止 Ingress

使用 openssl req -x509 -nodes 一次生成密钥和证书,再用 kubectl create secret tls 保存。Ingress 必须在 spec.tls 中填写主机和 secretName,才能为该主机使用该证书终止 TLS。它应与 spec.rules 中的 host 相同。

ServiceAccount 最小权限

Role 只在命名空间内生效。verbs 中未列出的动作不会获准,因此只需写 get、list、watch。RoleBinding 的主体用 --serviceaccount=<命名空间>:<名称> 格式指定。评分会使用 kubectl auth can-i 从边界两侧检查。

阻止令牌自动挂载

automountServiceAccountToken 同时存在于 ServiceAccount 和 Pod 中,Pod 侧优先。ServiceAccount 侧使用 kubectl patch serviceaccount 关闭,Pod 则直接写在 spec 中。两处都必须为 false。

只读集群审计员

跨命名空间权限应通过 ClusterRole 而非 Role 授予,并用 ClusterRoleBinding 绑定。主体类型不是 ServiceAccount,而是 Group,apiGroup 为 rbac.authorization.k8s.io。networkpolicies 不属于核心组,而属于 networking.k8s.io 组。

编写并连接 seccomp 配置文件

seccomp 配置文件通过 defaultAction 确定默认判定,并在 syscalls 数组中开放例外。如果默认是拒绝(SCMP_ACT_ERRNO),则必须提供允许列表。在 Pod 中,将 securityContext.seccompProfile 的 type 设为 Localhost,并在 localhostProfile 中填写相对于节点 seccomp 根目录的路径。

编写并连接 AppArmor 配置文件

AppArmor 配置文件采用 profile <名称> { ... } 块,并在其中写入访问规则。阻止所有写入的规则以 deny 开头,后接路径模式、权限字符,并以逗号结尾。Pod 侧使用 securityContext.appArmorProfile 字段。请使用字段方式,而不是旧的注解方式。

Pod 安全准入 restricted

Pod 安全准入通过命名空间标签启用。模式包括 enforce、audit、warn 三种,分别在 pod-security.kubernetes.io/<模式> 标签中填写级别。版本标签则在相同前缀后添加 -version。总共需要六个标签。

确认违规 Pod 被拒绝

拒绝消息输出到标准错误。如果重定向时漏掉 2>&1,会留下空文件。违规 Pod 无需刻意复杂化,只要完全不写 securityContext 即可。restricted 会同时要求四项设置,因此会直接拦截。

沙箱运行时工作负载

RuntimeClass 是集群范围资源,只需一个 handler 即可创建。在 Pod 模板中通过 runtimeClassName 引用。restricted 要求四项设置,其中两项写在 Pod 级别,另两项写在容器级别。如果 Deployment 已创建但没有 Pod,说明被 restricted 拦截。

将镜像固定到摘要

标签会变化,摘要不会变化。固定到摘要时,名称与值之间使用 @ 而不是冒号,并且不要同时写标签。固定到摘要后,没有必要把 imagePullPolicy 设为 Always。

镜像构建卫生

多阶段构建会使用至少两个 FROM,用 AS 命名前一阶段,再通过 COPY --from= 只复制产物。基础镜像也应固定到摘要而非标签。ADD 会获取远程资源并自动解压,因此难以预测会引入什么内容。USER 若使用名称,该名称必须存在于镜像中,因此数字 UID 更安全。

强制使用可信注册表

ValidatingAdmissionPolicy 使用 CEL 表达式进行判定;要真正阻止请求,绑定的 validationActions 中必须包含 Deny。只设置 Warn 只会发出警告,Pod 仍会创建。通过绑定的 matchResources 缩小作用范围。若只选择一个命名空间,可使用 kubernetes.io/metadata.name 标签。所有命名空间都会自动带有该标签。

编写审计策略

审计策略从上到下检查,并在第一条匹配规则处停止。因此,如果把宽泛规则放在前面,后面的细化规则永远不会使用。子资源使用 pods/exec 这样的斜线格式。将 omitStages 放在顶层会应用到所有规则。

连接审计日志

只写标志时,apiserver 找不到策略文件。静态 Pod 必须通过 hostPath 卷挂载节点文件系统,才能看到该路径。策略文件只需读取,而日志目录需要写入,因此两个挂载的 readOnly 设置不同。在 hostPath 中指定 type 可以区分文件与目录。

运行时不可变性

将根文件系统设为只读后,大多数应用会因无法写入临时文件而退出。因此只为需要写入的路径开放卷。直接挂载节点文件系统会破坏隔离,所以应使用 emptyDir。共享主机命名空间会让容器边界本身消失。