用攻击者的眼睛打开集群
本实验在真正的集群上运行
KCSA 考的是威胁模型和攻击面。要学会它,就得 真的去偷一偷看——从存储里取出明文,在没有权限的情况下读出 Secret,并看到审计日志里留下了它的痕迹。
假集群里没有存储,没有 apiserver 的认证链,也没有审计日志,所以这一切都只停留在文字上。嘴上听说“base64 不是加密”,和亲手从存储里取出明文,是两回事。
要避免与 CKS 重复。CKS 是 拦截 的实验,而这里的 KCSA,是用攻击来展示 为什么必须拦截。
第一次启动大约需要 2 分钟。
步骤
- 创建 Secret,并把 从存储中以明文取出 的内容写入
/root/kcsa/plaintext.txt。 - 打开 静态数据加密,并把明文消失这件事写入
/root/kcsa/encrypt.txt。 - 把旧 Secret 必须重写 才会被加密这件事,写入
/root/kcsa/rewrite.txt。 - 对
app应用 PSAenforce=restricted、enforce-version=v1.36。只授予runnerSA 在app中的 pods get、list、create,不要允许 secrets get。在/root/kcsa/leak.yaml中编写 namespace app 的leakPod。使用 restartPolicy=Never、runner SA、关闭自动令牌挂载、runAsNonRoot 和 UID 65532、RuntimeDefault seccomp、allowPrivilegeEscalation=false、capabilities drop ALL。一个容器用 busybox:1.36 运行command: [sha256sum, /s/password],并把 db Secret 卷 s 以只读方式挂载到 /s。不要用管理员创建来代替,要用--as=system:serviceaccount:app:runner创建。确认 Succeeded 之后,在/root/kcsa/escalate.txt中写下direct_secret=no、create_pod=yes、psa=restricted、secret_mount=readable,以及实际日志中的一行哈希,并说明 PSA 会拦住的运行设置和拦不住的数据访问边界。不要输出合成密钥的原文。 - 用匿名和默认 SA 去敲 apiserver 的门,把 认证边界 写入
/root/kcsa/anon.txt。 - 在
/etc/kubernetes/audit.yaml中写审计策略。第一条规则要对 core 组的secrets、configmaps、serviceaccounts/token,不限用户、动词和命名空间,以Metadata级别保护。接下来只把/healthz、/readyz、/livez及它们各自的/*子路径设为None,最后一条是无条件的Metadata。omitStages只能省略RequestReceived。总共使用 3 条规则,或者在兜底的默认规则之前再加上一条 RBAC 写入详细规则,共 4 条。可选的详细规则,只对rbac.authorization.k8s.io的 roles、rolebindings、clusterroles、clusterrolebindings 的 create、update、patch、delete、deletecollection,以RequestResponse记录。在/etc/rancher/k3s/config.yaml.d/90-labhub-audit.yaml中用kube-apiserver-arg+追加审计参数,以保留现有的加密设置,然后重启 k3s。日志限制为/var/log/kubernetes/audit.log、blocking 模式、maxsize=5、maxbackup=1、maxage=1。准备完成后,用提供的辅助工具python3 /opt/fixtures/audit-evidence.py capture --policy /etc/kubernetes/audit.yaml --log /var/log/kubernetes/audit.log --case /root/kcsa/audit-case.json --events /root/kcsa/audit-evidence.jsonl,收集合成 Secret 的创建 201、查询 200、拒绝 403、删除 200。保留四个不含正文的 Metadata 事件和策略指纹,并把结果说明写入/root/kcsa/audit.txt。辅助工具不会修改策略,也不会放入真实密钥。 - 把到目前为止的内容用 4C 和 STRIDE 整理,写入
/root/kcsa/model.txt。 - 在
/root/kcsa/report.md中写plaintext_found=yes、encrypted=yes、audit_lines=三行以及说明。
参考
- 存储是
/var/lib/rancher/k3s/server/db/state.db。直接用sqlite3打开。 - 值不是 base64,而是以 raw 形式存入。用
hex(value)取出,找明文。 - 重新启动 apiserver 之后,要等到
kubectl get --raw=/readyz变成ok。 - 常见误解:以为打开加密之后,旧 Secret 也会被加密。必须重写 才会改变。
- 常见误解:以为
can-i get secrets为 no 就安全。通过允许 Secret 卷的工作负载的创建权限,可以间接访问。Restricted 也允许 Secret 卷。
这是 70 分钟的实验,所以请在到期之前用+时间延长(最长 180 分钟)。会话结束时,工作成果会消失。
base64 不是加密
创建 Secret,并把 从存储中以明文取出 的内容写入 /root/kcsa/plaintext.txt。
用 sqlite3 打开存储,在 hex(value) 里找明文。
打开静态数据加密
打开 静态数据加密,并把明文消失这件事写入 /root/kcsa/encrypt.txt。
写一份 EncryptionConfiguration,用 --encryption-provider-config 传给 apiserver。
旧 Secret 必须重写才会改变
把旧 Secret 必须重写 才会被加密这件事,写入 /root/kcsa/rewrite.txt。
用 kubectl get secret -o json | kubectl replace -f - 重写。
Restricted 的 Pod 也能读取 Secret 卷
对 app 应用 PSA enforce=restricted、enforce-version=v1.36。
只授予 runner SA 在 app 中的 pods get、list、create,不要允许 secrets get。
在 /root/kcsa/leak.yaml 中编写 namespace app 的 leak Pod。使用 restartPolicy=Never、
runner SA、关闭自动令牌挂载、
runAsNonRoot 和 UID 65532、RuntimeDefault seccomp、allowPrivilegeEscalation=false、
capabilities drop ALL。一个容器用 busybox:1.36 运行
command: [sha256sum, /s/password],并把 db Secret 卷 s 以只读方式
挂载到 /s。不要用管理员创建来代替,要用 --as=system:serviceaccount:app:runner
创建。确认 Succeeded 之后,在 /root/kcsa/escalate.txt 中写下 direct_secret=no、
create_pod=yes、psa=restricted、secret_mount=readable,以及实际日志中的一行哈希,
并说明 PSA 会拦住的运行设置和拦不住的数据访问边界。不要输出合成密钥的原文。
发出请求的主体和 Pod 的 SA 是不同的。用 runner 来创建,并区分 PSA 的运行条件和对 Secret 的引用。
敲一敲认证边界
用匿名和默认 SA 去敲 apiserver 的门,把 认证边界 写入 /root/kcsa/anon.txt。
请用匿名请求和默认 SA 令牌直接调用 apiserver。
不留下记录,就等于没发生过
在 /etc/kubernetes/audit.yaml 中写审计策略。第一条规则要对 core 组的 secrets、configmaps、serviceaccounts/token,不限用户、动词和命名空间,以 Metadata 级别保护。接下来只把 /healthz、/readyz、/livez 及它们各自的 /* 子路径设为 None,最后一条是无条件的 Metadata。omitStages 只能省略 RequestReceived。总共使用 3 条规则,或者在兜底的默认规则之前再加上一条 RBAC 写入详细规则,共 4 条。可选的详细规则,只对 rbac.authorization.k8s.io 的 roles、rolebindings、clusterroles、clusterrolebindings 的 create、update、patch、delete、deletecollection,以 RequestResponse 记录。在 /etc/rancher/k3s/config.yaml.d/90-labhub-audit.yaml 中用 kube-apiserver-arg+ 追加审计参数,以保留现有的加密设置,然后重启 k3s。日志限制为 /var/log/kubernetes/audit.log、blocking 模式、maxsize=5、maxbackup=1、maxage=1。准备完成后,用提供的辅助工具 python3 /opt/fixtures/audit-evidence.py capture --policy /etc/kubernetes/audit.yaml --log /var/log/kubernetes/audit.log --case /root/kcsa/audit-case.json --events /root/kcsa/audit-evidence.jsonl,收集合成 Secret 的创建 201、查询 200、拒绝 403、删除 200。保留四个不含正文的 Metadata 事件和策略指纹,并把结果说明写入 /root/kcsa/audit.txt。辅助工具不会修改策略,也不会放入真实密钥。
保护规则必须先匹配。omitStages 不是删除正文。请区分策略的编写和实际完成事件的收集。
用 4C 和 STRIDE 整理
把到目前为止的内容用 4C 和 STRIDE 整理,写入 /root/kcsa/model.txt。
请把到目前为止看到的攻击,分别对应到 4C 的层和 STRIDE 的类别上。
学到了什么
在 /root/kcsa/report.md 中写 plaintext_found=yes、encrypted=yes、audit_lines= 三行以及说明。
写 plaintext_found=yes、encrypted=yes、audit_lines= 三行以及说明。