CKS 模拟考 B
目标
这是 B 场,与 A 场没有任何重复题目。 域及比例相同,但检验的能力完全不同。完成 A 后继续本场,可以发现仍薄弱的领域。
请在与真实 CKS 相同的条件下,于 120 分钟内完成 17 项任务。及格线为 67%,采用部分计分;17 项中通过 12 项即视为完成。
这是模拟考试。请先不要查看提示和答案,独立完成到底。 遇到卡住的任务,做好标记后先跳过,利用剩余时间再回来。可随时、多次评分,结果具有幂等性。
为什么重要
CKS 是唯一要求先通过 CKA 的考试。它假定你已经会操作 Kubernetes,因此题目更接近“找出并修复此配置中的风险”,而不是“创建某物”。考试检验防御与诊断,不涉及攻击技巧。
本场尤其考察缩小边界的方法:把权限缩到一个名称,为 token 加上受众和寿命,只写一次规则却把效力限制在一个命名空间。实际工作中,先大范围开放再收紧几乎不会发生;一开始就最小化开放范围才是真正能力。
考试环境
- 可访问文档为
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。 - 可在上述站内搜索,但结果不能离开允许列表。
- 已配置
kalias 与 bash completion,无需浪费时间创建。 - 每题按要求
ssh到指定主机;不支持嵌套 ssh,进入下一题前请exit。工具只存在于 SSH 目标主机。 - 复制为
Ctrl+Shift+C,粘贴为Ctrl+Shift+V。
本模拟环境的差异
这是运行于 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
- 创建命名空间
staging,并为带app=web而不是app=api标签的 Pod 创建 NetworkPolicyapi-allow。Ingress 只允许来自带env=frontend标签的命名空间中、带role=web标签的 Pod 的 TCP 8080 流量,阻止其余所有 Ingress。Egress 只允许 UDP 53 和 TCP 53,阻止其余所有 Egress。 - 将
/usr/local/bin/kubectl和/usr/local/bin/helm的 SHA-256 hash 按sha256sum原始输出格式保存到/root/exam/verify/binaries.sha256。必须使用绝对路径,并确保执行sha256sum -c时两项都通过。 - 在
/root/exam/kubelet/config.yaml中编写 kubelet 配置。apiVersion为kubelet.config.k8s.io/v1beta1,kind为KubeletConfiguration。关闭匿名访问,启用 webhook 认证,将授权模式设为Webhook,关闭未认证的只读端口,并启用 kernel default protection。
Cluster Hardening
- 在命名空间
staging中,使用户ci-deployer只能对 Deploymentweb执行get、patch、update;为此创建 Roleweb-deployer和 RoleBindingci-deployer-web。它不能修改同一命名空间的其他 Deployment,也不能列出 Deployment。 - 在命名空间
staging中创建 service accountagent和 Podcollector。Pod 使用agent,关闭默认 token 自动挂载,改用 projected volumetoken签发 audience 为vault、有效期 3600 秒的 service account token,文件名为token,挂载到/var/run/secrets/vault。容器名为app。 - 创建 ClusterRole
namespace-reader,允许对 core group 的pods、services、configmaps执行get、list、watch。让用户oncall仅在命名空间staging内拥有这些权限,并创建 RoleBindingoncall-reader,并确保其他命名空间无权读取,在staging中也不能读取 Secret。其他命名空间中不得读取任何内容,且不得读取 Secret。
System Hardening
- 在
/root/exam/seccomp/net-deny.json中编写 seccomp profile。默认 action 为SCMP_ACT_LOG,阻止socket和connectsyscall 时使用SCMP_ACT_ERRNO。然后在命名空间staging创建 Podsensor:Pod 级使用RuntimeDefault,仅容器app以Localhost方式使用路径profiles/net-deny.json的该 profile。 - 在
/root/exam/sudoers.d/deploy中编写 sudo 规则,使用户deploy无需密码、且只能以 root 执行/usr/bin/systemctl restart kubelet。不得存在命令为ALL的规则。文件必须通过visudo -cf语法检查。
Minimize Microservice Vulnerabilities
- 创建命名空间
payments,分阶段配置 Pod Security Admission。enforce为baseline,版本固定为v1.31;audit和warn为restricted,版本为latest。违反baseline的 Pod 必须被拒绝,只违反restricted的 Pod 应在警告下创建。 - 在命名空间
payments创建 Secretdb-cred,类型为默认的Opaque,key 为username和password。创建 Podreporter,通过 volumecred将该 Secret 只读挂载到/etc/db,文件权限为八进制0400。不得把 Secret 值放入环境变量。容器名为app。 - 在
/root/exam/encryption/config.yaml中编写静态加密配置。apiVersion为apiserver.config.k8s.io/v1,kind为EncryptionConfiguration。资源为secrets,provider 顺序必须是aescbc第一、identity最后。aescbckey 必须有名称,值为 32 字节数据的 base64。
Supply Chain Security
- 创建命名空间
supply,为访问 registryregistry.internal的用户deployer创建 Secretregcred。把 service accountpuller配置为使用该 Secret 拉取镜像,并让 Deploymentcatalog使用该 service account。容器名为app,镜像为registry.internal/catalog:1.4.2。 - 在命名空间
supply阻止浮动 tag。创建 ValidatingAdmissionPolicyno-latest-tag和 ValidatingAdmissionPolicyBindingno-latest-tag,要求 Pod 的所有 container 与所有 init container 镜像都不以:latest结尾。Binding 的validationActions为Deny,作用域仅为命名空间supply,不得影响其他命名空间。 - 在
/root/exam/imagepolicy/admission-config.yaml中编写 admission 配置。apiVersion为apiserver.config.k8s.io/v1,kind为AdmissionConfiguration,唯一 plugin 为ImagePolicyWebhook。其imagePolicy中kubeConfigFile为/etc/kubernetes/imagepolicy/kubeconfig.yaml,allowTTL50、denyTTL50、retryBackoff500,并将defaultAllow设为 false,使 webhook 不可达时不允许镜像。
Monitoring, Logging and Runtime Security
- 在
/root/exam/falco/rules.yaml中编写运行时检测规则。文件是条目列表,必须各有一个名为trusted_writers的list条目和名为Write below etc的rule条目。list 至少包含一个 item。rule 的priority为WARNING,desc和tags非空;condition同时包含open_write、/etc、trusted_writers;output包含%proc.name和%fd.name。 - 在
/root/exam/audit/quiet-policy.yaml中编写 audit policy。apiVersion为audit.k8s.io/v1,kind为Policy,规则恰好四条且顺序重要:第一,丢弃用户system:kube-proxy的watch对 core groupendpoints、services的访问,level 为None;第二,丢弃 groupsystem:authenticated对 non-resource URL/api*、/version的访问,level 为None;第三,记录命名空间payments中 core group 的secrets,level 为RequestResponse;第四,用不指定对象的Metadata规则接收其余全部请求。 - 创建命名空间
runtime,强制其中新建 Pod 的所有 container 都具有readOnlyRootFilesystem: true。创建 ValidatingAdmissionPolicyimmutable-root和 ValidatingAdmissionPolicyBindingimmutable-root,Binding 的validationActions为Deny,作用域仅为命名空间runtime,不得影响其他命名空间。
参考
- 重新设置 label 时使用
kubectl label ... --overwrite,否则已有 label 会报错。 kubectl auth can-i可在资源后附名称,例如kubectl auth can-i patch deployments/web -n staging --as=ci-deployer,可准确验证resourceNames权限。- 验证 admission policy 时使用
kubectl apply --dry-run=server,它会走完整 admission chain 而不留下对象。 - 三个常见错误:NetworkPolicy 的
from拆成两个条目会成为 OR;以resourceNames限制的规则不允许没有名称的list;CEL 中object.spec.initContainers可能不存在,未用has()包裹会漏过 init container。
同时使用两个 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 配置。apiVersion 为 kubelet.config.k8s.io/v1beta1,kind 为 KubeletConfiguration。关闭匿名访问,启用 webhook 认证,将授权模式设为 Webhook,关闭未认证的只读端口,并启用 kernel default protection。
匿名访问和 webhook 认证分别位于 authentication 下,授权模式是 authorization.mode。AlwaysAllow 会使认证强化失去意义。未认证只读端口是 readOnlyPort,关闭值为 0。kernel default protection 是 protectKernelDefaults。
把权限缩小到一个名称
在命名空间 staging 中,使用户 ci-deployer 只能对 Deployment web 执行 get、patch、update;为此创建 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 的 pods、services、configmaps 执行 get、list、watch。让用户 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,阻止 socket 和 connect syscall 时使用 SCMP_ACT_ERRNO。然后在命名空间 staging 创建 Pod sensor:Pod 级使用 RuntimeDefault,仅容器 app 以 Localhost 方式使用路径 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。enforce 为 baseline,版本固定为 v1.31;audit 和 warn 为 restricted,版本为 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 为 username 和 password。创建 Pod reporter,通过 volume cred 将该 Secret 只读挂载到 /etc/db,文件权限为八进制 0400。不得把 Secret 值放入环境变量。容器名为 app。
环境变量中的 Secret 会出现在进程列表、core dump 和 kubectl describe 中。文件方式只暴露给能读取路径的进程。defaultMode 设置文件权限,volumeMount 另有 readOnly。
静态数据加密配置
在 /root/exam/encryption/config.yaml 中编写静态加密配置。apiVersion 为 apiserver.config.k8s.io/v1,kind 为 EncryptionConfiguration。资源为 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 的 validationActions 为 Deny,作用域仅为命名空间 supply,不得影响其他命名空间。
CEL 的 all() 检查列表所有元素。object.spec.initContainers 可能不存在,必须用 has() 处理。真正阻止请求需要 validationActions 包含 Deny;作用域通过 binding 的 matchResources 缩小。
配置镜像策略 webhook
在 /root/exam/imagepolicy/admission-config.yaml 中编写 admission 配置。apiVersion 为 apiserver.config.k8s.io/v1,kind 为 AdmissionConfiguration,唯一 plugin 为 ImagePolicyWebhook。其 imagePolicy 中 kubeConfigFile 为 /etc/kubernetes/imagepolicy/kubeconfig.yaml,allowTTL 50、denyTTL 50、retryBackoff 500,并将 defaultAllow 设为 false,使 webhook 不可达时不允许镜像。
defaultAllow 决定 webhook 不可达时的行为。设为 true 会在 webhook 宕机时放行所有镜像。请显式配置。plugin 的 configuration 可内联,键名为 imagePolicy。
运行时检测规则
在 /root/exam/falco/rules.yaml 中编写运行时检测规则。文件是条目列表,必须各有一个名为 trusted_writers 的 list 条目和名为 Write below etc 的 rule 条目。list 至少包含一个 item。rule 的 priority 为 WARNING,desc 和 tags 非空;condition 同时包含 open_write、/etc、trusted_writers;output 包含 %proc.name 和 %fd.name。
Falco 文件是条目列表,首个 key 决定条目类型。list 有名称和 items;rule 有 desc、condition、output、priority、tags。output 中以 % 开头的是事件字段,应保留哪个进程写了哪个文件。
消除审计日志噪声
在 /root/exam/audit/quiet-policy.yaml 中编写 audit policy。apiVersion 为 audit.k8s.io/v1,kind 为 Policy,规则恰好四条且顺序重要:第一,丢弃用户 system:kube-proxy 的 watch 对 core group endpoints、services 的访问,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 的 validationActions 为 Deny,作用域仅为命名空间 runtime,不得影响其他命名空间。
要强制未来所有 Pod,必须在 admission 阶段执行。securityContext 及其中字段都可能不存在,CEL 中必须分两层使用 has()。作用域通过 binding 的 matchResources 限制,可使用 kubernetes.io/metadata.name namespace label。