bigywater.tech
Kubernetes · 64

Secret:base64 不是加密,etcd 里存了什么

Secret 里的值到底有多“保密”?base64 是加密吗?etcd 里存的是什么?只有 list 权限能不能看到值?这篇在真集群里把 Secret 从 kubectl、etcd、RBAC 和 Pod 里各看一遍。

230 字节
etcd 里 /registry/secrets/…/db 的值,明文直接可见
403 vs 200
只有 list 权限时 GET 单个被拒,LIST 却返回所有 Secret 的完整 data
tmpfs
Secret 卷在容器里的文件系统类型

base64 只是编码

实测 · 本机执行输出
  kubectl get secret db -o json 里 data.password='czNjcjN0LVBhNTU='
  base64 -d 之后='s3cr3t-Pa55'(任何能 get 这个 Secret 的人都能做)
  kubectl describe secret db 的 Data 部分:Data | ==== | password:  11 bytes
  用 stringData 写入明文:服务端存成 data.token='cGxhaW4tdGV4dC1pbi15YW1s'(= 'plain-text-in-yaml');返回的对象里 stringData=(没有)
  data 里写不是 base64 的值:退出码 1;Error from server (BadRequest): error when creating "STDIN": Secret in version "v1" cannot be handled as a Secret: illegal base64 data at input byte 3
  Secret 有 type 字段:db 的 type=Opaque

kubectl create secret --from-literal=password=s3cr3t-Pa55 之后,API 里的 data.password 是 czNjcjN0LVBhNTU=,base64 -d 一步还原。kubectl describe 只显示 password: 11 bytes,那只是不显示,不是不能读。stringData 是写入时的便利字段:服务端把它转成 data(base64),读回来时没有 stringData;在 data 里写不是 base64 的内容会直接失败:illegal base64 data at input byte 3。

etcd 里的样子

实测 · 本机执行输出
  etcd key /registry/secrets/k58/db:值共 230 字节,开头 b'k8s\x00\n\x0c\n\x02v1\x12\x06'(k8s\x00 是 protobuf 存储格式的魔数)
  值里能直接找到 password 明文 s3cr3t-Pa55:True;能找到 base64 形式 czNjcjN0LVBhNTU=:False(etcd 里直接存原始字节,不再 base64)
  把值里的不可见字节换成点:k8s.....v1..Secret........db....k58".*$57aadef9-5035-4fa2-8e21-b50b52a491012.8.B...........e..kubectl-create..Update..v1".........
  同样内容放进 ConfigMap:etcd 值开头 b'k8s\x00\n\x0f\n\x02v1\x12\t',也能直接找到明文:True(两者存储方式一样,Secret 没有额外保护)

etcd 里 /registry/secrets/k58/db 的值共 230 字节,开头是 k8s\x00(Kubernetes 的 protobuf 存储格式标记),把不可见字节换成点能读到 Secret、db、k58、kubectl-create 这些字符串;s3cr3t-Pa55 明文直接能找到,base64 形式反而找不到——etcd 存的是原始字节。同样的数据放进 ConfigMap,etcd 里的样子几乎一样,Secret 默认没有任何额外保护。所以 etcd 的备份文件、能直接访问 etcd 的人都等于拿到了所有 Secret。

list 就等于读

实测 · 本机执行输出
  只有 list 权限的 ServiceAccount:GET 单个 Secret → HTTP 403
    LIST 全部 Secret → HTTP 200;响应里带完整的 data:{'db': 's3cr3t-Pa55', 'sd': 'plain-text-in-yaml'}(list 的权限等于读全部值)
    kubectl auth can-i get secrets --as=…lister → no
    kubectl auth can-i list secrets --as=…lister → yes

给 ServiceAccount 只绑 list secrets:GET 单个 Secret 是 403,kubectl auth can-i get secrets 是 no;但 LIST 命名空间下全部 Secret 返回 200,响应里就带着每个 Secret 的完整 data,解码就是 s3cr3t-Pa55 和 plain-text-in-yaml。给 list 就等于给了读取所有值的权限,不是“只能看名字”。

挂进 Pod

实测 · 本机执行输出
  容器里 /etc/s 的挂载:tmpfs /etc/s tmpfs ro,relatime,size=8124516k,noswap 0 0(tmpfs,不落节点磁盘)
  /etc/s/password='s3cr3t-Pa55';环境变量 PW='s3cr3t-Pa55';/proc/1/environ 里能看到:True
  kubectl get pod -o yaml 里 env 只写引用:[{'name': 'PW', 'valueFrom': {'secretKeyRef': {'key': 'password', 'name': 'db'}}}](值不在 Pod 对象里)

Secret 作为卷挂进容器时,挂载点是 tmpfs … ro,不落节点磁盘;容器里 /etc/s/password 是明文。作为环境变量时,PW=s3cr3t-Pa55 进入进程环境,在 /proc/1/environ 里能看到,子进程会继承。Pod 对象里只有 secretKeyRef 引用,值不在 Pod 里,但谁能 exec 进这个容器,谁就能读。

分步演示

Secret 与 base64
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0),没有开启静态加密;etcd 的内容用控制面上 etcd 静态 Pod 里的 etcdctl 以 -w json 读出,在宿主机上解 base64 后检查字节;“只有 list 权限”的 ServiceAccount 令牌由 kubectl create token 取得,用 curl 风格的直接 API 请求;每种情形各一次
    • 没有验证:静态加密之后 etcd 里的样子(见下一篇);watch 权限是否同样返回完整内容;kubectl get secret -o yaml 的 managedFields 是否泄露;Secret 在 kubelet 缓存和日志里的保留;imagePullSecrets 与 kubernetes.io/dockerconfigjson 类型的格式;外部 Secret 存储(CSI Secrets Store)
    京ICP备2026067502号-1京公网安备11010502063487号