打开 etcd,密码以明文掉了出来
目标
亲自打开集群唯一的真相存储 etcd,用眼睛确认 Secret 是以明文存储的。并对比观察:RBAC 挡住的只是经过 apiserver 的 API 路径,直接连到 etcd 就绕过了这条边界。
为什么重要
Kubernetes 的所有对象最终都存储在 etcd 里。Secret 的 data 值显示为 base64,看起来像是被遮住了,但 base64 是编码,不是加密。如果不打开静态加密(encryption at rest),任何能够触及 etcd 的人,都能原样读到密码原文。
另一方面,RBAC 虽然强大,但它的控制 只适用于经过 apiserver 的请求。对于直接访问节点、etcd 数据目录,或者没有设置认证的 etcd 端口的路径,RBAC 无法介入。所以必须用 TLS、双向认证和静态加密,另外保护 etcd 本身。这个实验先以没有防御时的样子,展示“为什么需要这些”。
步骤
- 创建命名空间
kcsa-etcd。 - 创建 Secret
db-cred(password)和 ConfigMapapp-cfg(mode)。 - 不带证书连接 etcd 端点,把状态保存成文件。
- 直接从 etcd 读取 Secret,把密码原文提取到文件里。
- 判定该值是明文而不是加密,并记录下来。
- 用只读取 ConfigMap 的最小权限 ServiceAccount 确认 RBAC 边界。
- 整理保护 etcd 的三项控制(client TLS、peer TLS、静态加密)。
- 把观察结果以账簿的形式留在
/root/kcsa-etcd/report.txt中。
参考
- 这个实验里的 etcd 是为学习而设,没有 TLS 和认证。在生产集群里绝对不能这样放着。
- 可以用
kubectl auth can-i ... --as=system:serviceaccount:<ns>:<sa>直接确认 RBAC 的判定。 - 官方文档:Secret · 静态数据加密 · 集群组件.
打开观察对象命名空间
创建命名空间 kcsa-etcd。你要直接从 etcd 里窥视这个空间里的 Secret。
命名空间用 kubectl create namespace 创建。想让它运行多次也安全,就用 --dry-run=client -o yaml | kubectl apply -f -。
把一个密码托付给集群
在命名空间 kcsa-etcd 中创建 generic Secret db-cred,其中放入字面量 password=Pl4inEtcd!。在同一个命名空间中创建 ConfigMap app-cfg,放入键 mode=prod。
用 kubectl create secret generic <이름> --from-literal=키=값(占位符依次为名称、键与值)创建 Secret,用 kubectl create configmap <이름> --from-literal=키=값(占位符依次为名称、键与值)创建 ConfigMap。Secret 的 data 值是以 base64 编码存储的(这不是加密)。
不带证书就能直接连到 etcd
用 etcdctl 把 etcd 端点的状态以表格形式取出,保存到 /root/kcsa-etcd/etcd-status.txt。请确认不需要客户端证书就能连到 127.0.0.1:2379。
用 ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 endpoint status -w table 查看状态。这个集群的 etcd 没有设置 TLS 和认证,所以不带证书选项就能直接响应。把输出重定向到文件。
从存储底层捡起密码
直接读取 etcd 中存储的 db-cred Secret,只把其中的密码字符串写入 /root/kcsa-etcd/leaked.txt(只写值,不带多余的换行)。文件内容必须与真正的密码完全相同。
Secret 在 etcd 键 /registry/secrets/<네임스페이스>/<이름>(占位符依次为命名空间与名称)之下。etcdctl get <키>(占位符为键)的结果里混有二进制,所以用 grep -a 只筛出文本。值是什么,要你亲自找出来——把 Secret 的 data 做 base64 解码,就能知道原文,再确认这个字符串在 etcd 原始数据里也是原样存在的。
判定这不是加密
判定放在磁盘(etcd)上的那个值是否是加密的。如果没有加密,就在 /root/kcsa-etcd/encrypted.txt 里只写一个单词 no。
base64 是编码,不是加密。如果在 etcd 原始数据里用 grep 能直接抓到密码原文,就说明静态加密(encryption at rest)是关着的。判定结果请用小写的一个单词写下来。
RBAC 只守 API 路径
在命名空间 kcsa-etcd 中创建 ServiceAccount apponly 和 Role cfg-only(只对 ConfigMap 有 get、list),并把这个 Role 绑定到 apponly。这样 apponly 应该能读 ConfigMap,但读不了 Secret。
用 kubectl create role <이름> --verb=get --verb=list --resource=configmaps(占位符为名称)创建最小权限的 Role,用 kubectl create rolebinding 把它绑定到 ServiceAccount。可以用 kubectl auth can-i <동사> <자원> -n <ns> --as=system:serviceaccount:<ns>:<sa>(占位符依次为动词与资源)确认实际的判定。
写下保护 etcd 的三件事
以阅读材料为依据,把保护 etcd 的三项控制,恰好写成三行,保存到 /root/kcsa-etcd/mitigations.txt——client-tls=required、peer-tls=required、encryption-at-rest=required。
etcd 的安全有三条轴:客户端与 etcd 之间的 TLS 和双向认证,etcd 节点之间(peer)的 TLS,以及 apiserver 的静态加密(EncryptionConfiguration)。三个键都写成 required。键名和拼写必须与指示原样一致。
把观察到的以账簿的形式留下来
在 /root/kcsa-etcd/report.txt 中恰好写四行——etcd-auth=none、secret-on-disk=plaintext、rbac-blocks-apponly=yes、etcd-bypasses-rbac=yes。值必须与前面步骤观察到的实际状态一致。
评分器会把这四行与实际状态核对。请用前面步骤的结果来填写:etcd 是否不经认证就连上了(none)、Secret 是否以明文存放(plaintext)、apponly 是否读不了 Secret(yes),以及直接访问 etcd 是否绕过了那条 RBAC 边界(yes)。