CKS 模拟考 A
目标
在与真实 CKS 相同的条件下,于 120 分钟内完成 17 道任务。及格线为 67%,并采用部分计分制,因此通过 17 道题中的 12 道即视为完成。
这是模拟考试。请先不看提示和答案,独立完成全部题目。 遇到卡住的题目,最好先做标记并跳过,等剩余时间再回来处理。 可以随时评分,多次评分也不会改变结果。
为什么重要
CKS 是唯一要求先通过 CKA 的考试。它假定你已经具备操作 Kubernetes 的能力,因此题目与其说是“请创建什么”,不如说更接近“找出这个配置中的风险并修复”。考试考查防御和诊断,不涉及攻击技术。
实际工作也是如此。集群遭到入侵的入口通常不是新功能,而是沿用默认值的地方:命名空间没有启用 Pod 安全准入,默认服务账户令牌挂载到所有 Pod,镜像又以可变标签部署。本考试考查的正是这份清单。
考试环境(真实考试中确认的事实)
- 可查看的文档包括
kubernetes.io/docs、kubernetes.io/blog、falco.org/docs、kubernetes-sigs.github.io/bom、etcd.io/docs、kubernetes.github.io/ingress-nginx、docs.cilium.io、istio.io/latest/docs。 - 允许在文档站点内搜索,但搜索结果不得跳转到允许列表之外。
k别名和 bash 自动补全已配置好。不要浪费时间创建别名。- 每道任务都需通过
ssh连接到指定主机操作,不支持嵌套 ssh。进入下一题前请执行exit。 - 工具只存在于通过 SSH 连接的主机上。
- 复制使用
Ctrl+Shift+C,粘贴使用Ctrl+Shift+V。
本模拟考试环境的差异
本实验的集群是在 Pod 内运行的单人集群。kube-apiserver 是真实的,因此清单、RBAC、Pod 安全准入和准入策略会**真实运行并实际拒绝请求。**但由于没有真正运行容器的运行时,无法验证以下两项:
- **NetworkPolicy 会被应用,但不会发生实际拦截。**这是因为没有 CNI。第 1、2 题只检查策略对象是否正确。
- **seccomp 和 AppArmor 配置文件不会加载到内核。**第 7、8 题检查配置文件内容及其与 Pod 的连接。
其余项目都会在活动集群中重新计算并评分。权限通过 kubectl auth can-i 检查,准入策略通过服务器 dry-run 重新查询当时的判定。
步骤
集群设置
- 创建命名空间
prod,并为其中所有 Pod 创建同时默认拒绝入站和出站流量的 NetworkPolicydefault-deny。不要添加任何允许规则。 - 在命名空间
prod中,阻止带有app=web标签的 Pod 访问节点元数据端点169.254.169.254/32,但保持其他出站流量开放,并将该 NetworkPolicy 命名为deny-metadata。 - 使用
CN=shop.internal的自签名证书在命名空间prod中创建 TLS Secretweb-tls,并为主机shop.internal创建使用该 Secret 终止 TLS 的 Ingressweb。后端是 Serviceweb的 80 端口。
集群加固
- 在命名空间
prod中创建 ServiceAccountreport-runner,使该账户只能在prod中对 Pod 执行get、list、watch,并为此创建 Rolepod-reader与 RoleBindingreport-runner-pod-reader。不要授予其他权限。 - 禁止命名空间
prod的defaultServiceAccount 自动挂载令牌。然后在prod中创建 Podfrontend,使用 ServiceAccountreport-runner,并在 Pod 规约中也关闭令牌自动挂载。 - 使安全审计组
security-audit只能在整个集群中读取 Pod、命名空间和 NetworkPolicy,并为此创建 ClusterRolesecurity-auditor和 ClusterRoleBindingsecurity-auditor。不要授予写权限或 Secret 访问权限。
系统加固
- 在
/root/exam/seccomp/audit.json中编写 seccomp 配置文件。默认动作是SCMP_ACT_ERRNO,并且至少要用SCMP_ACT_ALLOW放行若干系统调用。然后在命名空间prod中创建 Podprobe,以Localhost方式从路径profiles/audit.json使用该配置文件。 - 在
/root/exam/apparmor/k8s-deny-write中编写 AppArmor 配置文件。配置文件名为k8s-deny-write,必须包含#include <tunables/global>行以及阻止所有写入的deny /** w,规则。然后在命名空间prod中创建 Deploymentlogshipper,使 Pod 模板的securityContext.appArmorProfile以Localhost方式使用该配置文件。
减少微服务漏洞
- 创建命名空间
payments,将 Pod 安全准入的enforce、audit、warn三种模式全部设为restricted,并将三种模式的版本固定为latest。 - 尝试在命名空间
payments中应用违反restricted的 Podbad-pod,将包括标准错误在内的拒绝响应保存到/root/exam/denied.txt。Pod 不得实际创建。 - 创建 RuntimeClass
gvisor(handler 为runsc),并在命名空间payments中创建副本数为 2 的 Deploymentcheckout。Pod 模板使用该 RuntimeClass,且必须通过restricted。容器名为app;在 Pod 级别设置runAsNonRoot: true和seccompProfile.type: RuntimeDefault,在容器级别设置allowPrivilegeEscalation: false、capabilities.drop: [ALL]、readOnlyRootFilesystem: true。
供应链安全
- 创建命名空间
supply和 Deploymentpayments-api。容器名为api,镜像必须固定到摘要而不是标签。值为registry.internal/payments-api@sha256:140eab0459241fb1643767dd4cc3576d282b0059a6328521e714cb545fa4adea,imagePullPolicy为IfNotPresent。 - 在
/root/exam/Dockerfile中编写构建部署镜像的 Dockerfile。它必须是分离构建阶段与运行阶段的多阶段构建,将所有FROM基础镜像固定到@sha256:摘要,并且只能通过COPY --from=获取产物。不要使用ADD;最后的USER必须是非 0 的数字 UID;不要在ENV或ARG中留下疑似秘密的名称。 - 阻止使用未经批准的注册表镜像。创建 ValidatingAdmissionPolicy
trusted-images和 ValidatingAdmissionPolicyBindingtrusted-images,要求所有容器镜像都以registry.internal/开头。绑定的validationActions为Deny,作用范围仅限命名空间supply,不得影响其他命名空间。
监控、日志与运行时安全
- 在
/root/exam/audit/policy.yaml中编写审计策略。apiVersion为audit.k8s.io/v1,kind为Policy,顶层omitStages只有RequestReceived。规则恰好三条,顺序很重要。第一,记录核心组的secrets和configmaps,级别为Metadata。第二,记录pods/exec、pods/attach、pods/portforward,级别为RequestResponse。第三,用不指定对象的Metadata规则接收其余全部请求。 - 在
/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-policy和audit-logs;策略挂载必须为readOnly: true,日志挂载不得为只读。 - 在命名空间
prod中创建 Deploymentledger。容器名为app,并设置readOnlyRootFilesystem: true、allowPrivilegeEscalation: false、capabilities.drop: [ALL]。需要写入的/tmp由emptyDir卷scratch提供;不要使用hostPath卷以及hostNetwork、hostPID、hostIPC。
参考
- 重新设置标签时请使用
kubectl label ... --overwrite。如果不加该选项,遇到已有标签会报错。 - Pod 安全准入不会阻止 Deployment,而会阻止该 Deployment 创建的Pod。如果 Deployment 已创建却没有任何 Pod,请使用
kubectl -n <네임스페이스> describe rs查看原因。 - 不要猜测授予 ServiceAccount 的权限是否正确,请使用
kubectl auth can-i <동사> <자원> --as=system:serviceaccount:<ns>:<이름>检查。组可通过--as-group模拟。 - 两个常见错误:如果 NetworkPolicy 中不写
policyTypes,出站流量便不受控制。此外,automountServiceAccountToken同时存在于 ServiceAccount 和 Pod 中,Pod 侧优先。
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。共享主机命名空间会让容器边界本身消失。