LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

CKS 模拟考 B

在 LabHub 中继续学习

目标

这是 B 场,与 A 场没有任何重复题目。 域及比例相同,但检验的能力完全不同。完成 A 后继续本场,可以发现仍薄弱的领域。

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

这是模拟考试。请先不要查看提示和答案,独立完成到底。 遇到卡住的任务,做好标记后先跳过,利用剩余时间再回来。可随时、多次评分,结果具有幂等性。

为什么重要

CKS 是唯一要求先通过 CKA 的考试。它假定你已经会操作 Kubernetes,因此题目更接近“找出并修复此配置中的风险”,而不是“创建某物”。考试检验防御与诊断,不涉及攻击技巧。

本场尤其考察缩小边界的方法:把权限缩到一个名称,为 token 加上受众和寿命,只写一次规则却把效力限制在一个命名空间。实际工作中,先大范围开放再收紧几乎不会发生;一开始就最小化开放范围才是真正能力。

考试环境

本模拟环境的差异

这是运行于 Pod 内的单人集群。kube-apiserver 真实运行,因此 manifest、RBAC、Pod Security Admission 与 admission policy 会真实生效并真实拒绝。但没有实际运行容器的 runtime,因此 NetworkPolicy 不会真正阻断(第 1 题只检查对象),seccomp profile 不会载入 kernel(第 7 题检查文件与 wiring),第 8 题 sudo 规则不会应用于本 Pod(仅检查内容和 visudo 语法)。其余均从实时集群重新计算评分:权限用 kubectl auth can-i,admission 与 PSA 用 server dry-run。

步骤

Cluster Setup

  1. 创建命名空间 staging,并为带 app=web 而不是 app=api 标签的 Pod 创建 NetworkPolicy api-allow。Ingress 只允许来自带 env=frontend 标签的命名空间中、带 role=web 标签的 Pod 的 TCP 8080 流量,阻止其余所有 Ingress。Egress 只允许 UDP 53 和 TCP 53,阻止其余所有 Egress。
  2. /usr/local/bin/kubectl/usr/local/bin/helm 的 SHA-256 hash 按 sha256sum 原始输出格式保存到 /root/exam/verify/binaries.sha256。必须使用绝对路径,并确保执行 sha256sum -c 时两项都通过。
  3. /root/exam/kubelet/config.yaml 中编写 kubelet 配置。apiVersionkubelet.config.k8s.io/v1beta1kindKubeletConfiguration。关闭匿名访问,启用 webhook 认证,将授权模式设为 Webhook,关闭未认证的只读端口,并启用 kernel default protection。

Cluster Hardening

  1. 在命名空间 staging 中,使用户 ci-deployer 只能对 Deployment web 执行 getpatchupdate;为此创建 Role web-deployer 和 RoleBinding ci-deployer-web。它不能修改同一命名空间的其他 Deployment,也不能列出 Deployment。
  2. 在命名空间 staging 中创建 service account agent 和 Pod collector。Pod 使用 agent,关闭默认 token 自动挂载,改用 projected volume token 签发 audience 为 vault、有效期 3600 秒的 service account token,文件名为 token,挂载到 /var/run/secrets/vault。容器名为 app
  3. 创建 ClusterRole namespace-reader,允许对 core group 的 podsservicesconfigmaps 执行 getlistwatch。让用户 oncall 仅在命名空间 staging 内拥有这些权限,并创建 RoleBinding oncall-reader,并确保其他命名空间无权读取,在 staging 中也不能读取 Secret。其他命名空间中不得读取任何内容,且不得读取 Secret。

System Hardening

  1. /root/exam/seccomp/net-deny.json 中编写 seccomp profile。默认 action 为 SCMP_ACT_LOG,阻止 socketconnect syscall 时使用 SCMP_ACT_ERRNO。然后在命名空间 staging 创建 Pod sensor:Pod 级使用 RuntimeDefault,仅容器 appLocalhost 方式使用路径 profiles/net-deny.json 的该 profile。
  2. /root/exam/sudoers.d/deploy 中编写 sudo 规则,使用户 deploy 无需密码、且只能以 root 执行 /usr/bin/systemctl restart kubelet。不得存在命令为 ALL 的规则。文件必须通过 visudo -cf 语法检查。

Minimize Microservice Vulnerabilities

  1. 创建命名空间 payments,分阶段配置 Pod Security Admission。enforcebaseline,版本固定为 v1.31auditwarnrestricted,版本为 latest。违反 baseline 的 Pod 必须被拒绝,只违反 restricted 的 Pod 应在警告下创建。
  2. 在命名空间 payments 创建 Secret db-cred,类型为默认的 Opaque,key 为 usernamepassword。创建 Pod reporter,通过 volume cred 将该 Secret 只读挂载到 /etc/db,文件权限为八进制 0400。不得把 Secret 值放入环境变量。容器名为 app
  3. /root/exam/encryption/config.yaml 中编写静态加密配置。apiVersionapiserver.config.k8s.io/v1kindEncryptionConfiguration。资源为 secrets,provider 顺序必须是 aescbc 第一、identity 最后。aescbc key 必须有名称,值为 32 字节数据的 base64。

Supply Chain Security

  1. 创建命名空间 supply,为访问 registry registry.internal 的用户 deployer 创建 Secret regcred。把 service account puller 配置为使用该 Secret 拉取镜像,并让 Deployment catalog 使用该 service account。容器名为 app,镜像为 registry.internal/catalog:1.4.2
  2. 在命名空间 supply 阻止浮动 tag。创建 ValidatingAdmissionPolicy no-latest-tag 和 ValidatingAdmissionPolicyBinding no-latest-tag,要求 Pod 的所有 container 与所有 init container 镜像都不以 :latest 结尾。Binding 的 validationActionsDeny,作用域仅为命名空间 supply,不得影响其他命名空间。
  3. /root/exam/imagepolicy/admission-config.yaml 中编写 admission 配置。apiVersionapiserver.config.k8s.io/v1kindAdmissionConfiguration,唯一 plugin 为 ImagePolicyWebhook。其 imagePolicykubeConfigFile/etc/kubernetes/imagepolicy/kubeconfig.yamlallowTTL 50、denyTTL 50、retryBackoff 500,并将 defaultAllow 设为 false,使 webhook 不可达时不允许镜像。

Monitoring, Logging and Runtime Security

  1. /root/exam/falco/rules.yaml 中编写运行时检测规则。文件是条目列表,必须各有一个名为 trusted_writerslist 条目和名为 Write below etcrule 条目。list 至少包含一个 item。rule 的 priorityWARNINGdesctags 非空;condition 同时包含 open_write/etctrusted_writersoutput 包含 %proc.name%fd.name
  2. /root/exam/audit/quiet-policy.yaml 中编写 audit policy。apiVersionaudit.k8s.io/v1kindPolicy,规则恰好四条且顺序重要:第一,丢弃用户 system:kube-proxywatch 对 core group endpointsservices 的访问,level 为 None;第二,丢弃 group system:authenticated 对 non-resource URL /api*/version 的访问,level 为 None;第三,记录命名空间 payments 中 core group 的 secrets,level 为 RequestResponse;第四,用不指定对象的 Metadata 规则接收其余全部请求。
  3. 创建命名空间 runtime,强制其中新建 Pod 的所有 container 都具有 readOnlyRootFilesystem: true。创建 ValidatingAdmissionPolicy immutable-root 和 ValidatingAdmissionPolicyBinding immutable-root,Binding 的 validationActionsDeny,作用域仅为命名空间 runtime,不得影响其他命名空间。

参考

同时使用两个 selector 的允许列表

创建命名空间 staging,并为带 app=api 标签的 Pod 创建 NetworkPolicy api-allow。Ingress 只允许来自带 env=frontend 标签的命名空间中、带 role=web 标签的 Pod 的 TCP 8080 流量,阻止其余所有 Ingress。Egress 只允许 UDP 53 和 TCP 53,阻止其余所有 Egress。

在同一个 from 条目中同时写 namespaceSelector 和 podSelector,才会只选择同时满足两个条件的 Pod。拆成两个条目会变成满足任一条件即可,扩大开放范围。policyTypes 必须同时写 Ingress 和 Egress。Egress 规则只写 ports 而不写 to,表示不限制目的地,只限制端口。

部署前验证二进制文件

/usr/local/bin/kubectl/usr/local/bin/helm 的 SHA-256 hash 按 sha256sum 原始输出格式保存到 /root/exam/verify/binaries.sha256。必须使用绝对路径,并确保执行 sha256sum -c 时两项都通过。

sha256sum 的输出就是 sha256sum -c 的输入格式。一次传入多个文件会输出多行。不要手工抄写 hash,应直接重定向输出;相对路径会使验证结果依赖当前工作目录。

强化 kubelet 配置

/root/exam/kubelet/config.yaml 中编写 kubelet 配置。apiVersionkubelet.config.k8s.io/v1beta1kindKubeletConfiguration。关闭匿名访问,启用 webhook 认证,将授权模式设为 Webhook,关闭未认证的只读端口,并启用 kernel default protection。

匿名访问和 webhook 认证分别位于 authentication 下,授权模式是 authorization.mode。AlwaysAllow 会使认证强化失去意义。未认证只读端口是 readOnlyPort,关闭值为 0。kernel default protection 是 protectKernelDefaults。

把权限缩小到一个名称

在命名空间 staging 中,使用户 ci-deployer 只能对 Deployment web 执行 getpatchupdate;为此创建 Role web-deployer 和 RoleBinding ci-deployer-web。它不能修改同一命名空间的其他 Deployment,也不能列出 Deployment。

resourceNames 会将规则限制到指定对象。list 请求没有名称,因此不会被该规则允许。Deployment 位于 apps API group。subject kind 为 User,apiGroup 为 rbac.authorization.k8s.io。使用带资源名称的 kubectl auth can-i patch deployments/web 进行确认。

限定受众和寿命的 token

在命名空间 staging 中创建 service account agent 和 Pod collector。Pod 使用 agent,关闭默认 token 自动挂载,改用 projected volume token 签发 audience 为 vault、有效期 3600 秒的 service account token,文件名为 token,挂载到 /var/run/secrets/vault。容器名为 app

在 projected volume 的 sources 中使用 serviceAccountToken,kubelet 会签发短期 token。audience 限制使用方,expirationSeconds 设置寿命,path 设置文件名。关闭 automountServiceAccountToken 不会阻止显式 projected token。

规则只写一次,效力限于一个命名空间

创建 ClusterRole namespace-reader,允许对 core group 的 podsservicesconfigmaps 执行 getlistwatch。让用户 oncall 仅在命名空间 staging 内拥有这些权限,并创建 RoleBinding oncall-reader。其他命名空间中不得读取任何内容,且不得读取 Secret。

RoleBinding 的 roleRef 可以指向 ClusterRole,此时规则效力被限制在 RoleBinding 所在命名空间。若用 ClusterRoleBinding 则扩散到整个集群。subject kind 为 User,apiGroup 为 rbac.authorization.k8s.io。

在容器级覆盖 seccomp

/root/exam/seccomp/net-deny.json 中编写 seccomp profile。默认 action 为 SCMP_ACT_LOG,阻止 socketconnect syscall 时使用 SCMP_ACT_ERRNO。然后在命名空间 staging 创建 Pod sensor:Pod 级使用 RuntimeDefault,仅容器 appLocalhost 方式使用路径 profiles/net-deny.json 的该 profile。

defaultAction 决定默认行为,syscalls 数组写例外。securityContext 可位于 Pod 或容器级,容器级优先。Localhost 的 localhostProfile 是相对于节点 seccomp root 的路径。

最小化主机账户权限

/root/exam/sudoers.d/deploy 中编写 sudo 规则,使用户 deploy 无需密码、且只能以 root 执行 /usr/bin/systemctl restart kubelet。不得存在命令为 ALL 的规则。文件必须通过 visudo -cf 语法检查。

单行规则形如“用户 主机=(执行账户) 标签: 命令”。免密码标签是 NOPASSWD:。命令使用绝对路径并写明参数即可限制参数。用 visudo -cf 文件名检查。

分阶段提高 Pod Security Admission

创建命名空间 payments,分阶段配置 Pod Security Admission。enforcebaseline,版本固定为 v1.31auditwarnrestricted,版本为 latest。违反 baseline 的 Pod 必须被拒绝,只违反 restricted 的 Pod 应在警告下创建。

Pod Security Admission 通过 namespace label 启用,每种 mode 可单独设 level。enforce 阻止请求,audit 和 warn 只记录或警告。版本 label 在相同前缀后加 -version,共需六个 label。

Secret 只能作为文件

在命名空间 payments 创建 Secret db-cred,类型为默认的 Opaque,key 为 usernamepassword。创建 Pod reporter,通过 volume cred 将该 Secret 只读挂载到 /etc/db,文件权限为八进制 0400。不得把 Secret 值放入环境变量。容器名为 app

环境变量中的 Secret 会出现在进程列表、core dump 和 kubectl describe 中。文件方式只暴露给能读取路径的进程。defaultMode 设置文件权限,volumeMount 另有 readOnly。

静态数据加密配置

/root/exam/encryption/config.yaml 中编写静态加密配置。apiVersionapiserver.config.k8s.io/v1kindEncryptionConfiguration。资源为 secrets,provider 顺序必须是 aescbc 第一、identity 最后。aescbc key 必须有名称,值为 32 字节数据的 base64。

provider 顺序就是策略:写入使用第一个,读取从上到下尝试。identity 在前会明文写入,完全省略则无法读取旧明文。aescbc key 必须恰好 32 字节,可用 head -c 32 /dev/urandom | base64 生成。

私有 registry 凭据

创建命名空间 supply,为访问 registry registry.internal 的用户 deployer 创建 Secret regcred。把 service account puller 配置为使用该 Secret 拉取镜像,并让 Deployment catalog 使用该 service account。容器名为 app,镜像为 registry.internal/catalog:1.4.2

kubectl create secret docker-registry 会创建 kubernetes.io/dockerconfigjson 类型;普通 Opaque Secret 不会被 kubelet 当作 registry 凭据。service account 使用 imagePullSecrets,Pod 使用 serviceAccountName。

阻止浮动 tag

在命名空间 supply 阻止浮动 tag。创建 ValidatingAdmissionPolicy no-latest-tag 和 ValidatingAdmissionPolicyBinding no-latest-tag,要求 Pod 的所有 container 与所有 init container 镜像都不以 :latest 结尾。Binding 的 validationActionsDeny,作用域仅为命名空间 supply,不得影响其他命名空间。

CEL 的 all() 检查列表所有元素。object.spec.initContainers 可能不存在,必须用 has() 处理。真正阻止请求需要 validationActions 包含 Deny;作用域通过 binding 的 matchResources 缩小。

配置镜像策略 webhook

/root/exam/imagepolicy/admission-config.yaml 中编写 admission 配置。apiVersionapiserver.config.k8s.io/v1kindAdmissionConfiguration,唯一 plugin 为 ImagePolicyWebhook。其 imagePolicykubeConfigFile/etc/kubernetes/imagepolicy/kubeconfig.yamlallowTTL 50、denyTTL 50、retryBackoff 500,并将 defaultAllow 设为 false,使 webhook 不可达时不允许镜像。

defaultAllow 决定 webhook 不可达时的行为。设为 true 会在 webhook 宕机时放行所有镜像。请显式配置。plugin 的 configuration 可内联,键名为 imagePolicy。

运行时检测规则

/root/exam/falco/rules.yaml 中编写运行时检测规则。文件是条目列表,必须各有一个名为 trusted_writerslist 条目和名为 Write below etcrule 条目。list 至少包含一个 item。rule 的 priorityWARNINGdesctags 非空;condition 同时包含 open_write/etctrusted_writersoutput 包含 %proc.name%fd.name

Falco 文件是条目列表,首个 key 决定条目类型。list 有名称和 items;rule 有 desc、condition、output、priority、tags。output 中以 % 开头的是事件字段,应保留哪个进程写了哪个文件。

消除审计日志噪声

/root/exam/audit/quiet-policy.yaml 中编写 audit policy。apiVersionaudit.k8s.io/v1kindPolicy,规则恰好四条且顺序重要:第一,丢弃用户 system:kube-proxywatch 对 core group endpointsservices 的访问,level 为 None;第二,丢弃 group system:authenticated 对 non-resource URL /api*/version 的访问,level 为 None;第三,记录命名空间 payments 中 core group 的 secrets,level 为 RequestResponse;第四,用不指定对象的 Metadata 规则接收其余全部请求。

audit policy 从上到下匹配并在首条命中处停止,所以丢弃规则必须在宽泛规则之前。可用 users、userGroups、verbs、namespaces、nonResourceURLs 缩小范围。None 表示不记录。non-resource path 单独配置。

在 admission 阶段强制只读 root

创建命名空间 runtime,强制其中新建 Pod 的所有 container 都具有 readOnlyRootFilesystem: true。创建 ValidatingAdmissionPolicy immutable-root 和 ValidatingAdmissionPolicyBinding immutable-root,Binding 的 validationActionsDeny,作用域仅为命名空间 runtime,不得影响其他命名空间。

要强制未来所有 Pod,必须在 admission 阶段执行。securityContext 及其中字段都可能不存在,CEL 中必须分两层使用 has()。作用域通过 binding 的 matchResources 限制,可使用 kubernetes.io/metadata.name namespace label。