使用secret传递敏感数据
了解Secret
为了存储与分发此类信息,Kubemetes提供了一种称为Secret的单独资源对象。Secret结构与ConfigMap类似,均是键/值对的映射。Secret的使用方法也与ConfigMap相同,可以
将Secret条目作为环境变量传递给容器
将Secret条目暴露为卷中的文件
Kubernetes通过仅仅将Secret分发到需要访问Secret的pod所在的机器节点来保障其安全性。另外,Secret只会存储在节点的内存中,永不写入物理存储,这样从节点上删除Secret时就不需要擦除磁盘了。
对于主节点本身(尤其是etcd),Secret通常以非加密形式存储,这就需要保障主节点的安全从而确保存储在Secret中的敏感数据的安全性。这种保障不仅仅是对etcd存储的安全性保障,同样包括防止未授权用户对API服务器的访问,这是因为任何人都能通过创建pod并将Secret挂载来获得此类敏感数据。
从Secret与ConfigMap中做出正确选择是势在必行的,选择依据相对简单:
·采用ConfigMap存储非敏感的文本配置数据。 采用Secret存储天生敏感的数据,通过键来引用,如果一个配置文件同时包含敏感与非敏感数据,该文件应该被存储在Secret中。
默认令牌Secret
对任意一个pod使用命令kubectl describe pod,输出往往包含如下信息:
1 | default-token-6nw87: |
每个pod都会被自动挂载上一个secret卷,这个卷引用的是前面kubectl describe输出中的一个叫作default-token-cfee9的Secret。由于Secret也是资源对象,因此可以通过kubectl get secrets命令从Secret列表中找到这个
1 | Name: default-token-6nw87 |
可以看出这个Secret包含三个条目——ca.crt、namespace与token,包含了从pod内部安全访问Kubemetes API服务器所需的全部信息。尽管你希望做到用程序对Kubernetes的完全无感知,然而在除了直连Kubernetes别无他法的情况下你将会使用到secret卷提供的文件。
kubectl describe pod命令会显示secret卷被挂载的位置:
1 | Mounts: |
注意default-token Secret默认会被挂载至每个容器。可以通过设置pod定义中的automountServiceAccountToken字段为false、或者设置pod使用的服务账户中的相同字段为false来关闭这种默认行为
可以通过exec观察到被secret卷挂载的文件夹下包含三个文件
1 | [root@VM-20-9-centos ~]# kubectl exec nginx-hexo-blog-7b4f5b78cf-4p5hb ls /var/run/secrets/kubernetes.io/serviceaccount |
创建Secret
创建私钥和证书,用于nginx容器 https的配置
1 | openssl genrsa -out https.key 2048 |
创建一个内容为字符串bar的文件foo
1 | echo bar > foo |
使用kubectl create secret 命令有这两个文件创建Secret
1 | kubectl create secret generic https --from-file=https.key --from-file=https.cert --from-file=foo |
对比configmap和Secret
Secret和configmap仍有比较大的差距。
configmap的yaml格式定义
1 | [root@VM-20-9-centos ~]# kubectl get configmap nginx-config -o yaml |
Secret的yaml格式定义
[root@VM-20-9-centos ~]# kubectl get secret https -o yaml
apiVersion: v1
data:
foo: YmFyCg==
https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0...
https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVkt...
注意到两者的区别了吗?secret条目的内容会被以Base64格式编码,而configmap则以纯文本展示,这种区别导致在处理YAML和JSON格式的Secret时会稍许麻烦,需要在设置和读取相关条目时进行编解码。
为二进制数据创建Secret
采用Base64编码的原因很简单。secret的条目可以涵盖二进制数据,而不仅仅是纯文本。Base64编码可以将二进制数据转换为纯文本,以YAML或JSON格式展示
Secret甚至可以被用来存储非敏感二进制数据。需要注意的是,Secret的大小仅限于1MB。
stringData字段
由于并非所有的敏感数据都是以二进制的,k8s允许Secret通过StringData设置纯文本值为条目,StringData是只写的而非只读,可以被设置为条目值,通过kubectl get po -o yaml命令不会显示stringData字段,相反stringdata所有的条目会被Base64解析到data字段下
创建一个带有stringData字段的Secret
1 | apiVersion: v1 |
可以看到以YAML格式展示创建的secret,username字段与创建时的条目不太相符,你可能认为这是base64加密导致的原因。
1 | [root@VM-20-9-centos k8s]# kubectl get secret mysecret -o yaml |
通过base64的decode解密,可以看到解密出的信息正是创建stringdata条目时的信息,说明stringdata的条目被Base64解析到了data字段
1 | [root@VM-20-9-centos k8s]# echo "YWRtaW5pc3RyYXRvcg=="|base64 -d |
将Secret应用到pod中
修改nginx configmap以开启HTTPS
为了开启HTTPS,需要修改pod中nginx.conf的配置,在此之前,以将nginx.conf以configmap的方式应用到Pod中。
1 | kubectl edit configmap nginx-config |
上面配置了服务器从/etc/nginx/ssl中读取证书与密钥文件,因此之后要将secret挂载于此。
挂载nginx-https-tls secret至pod
1 | piVersion: apps/v1 |
修改nginx configmap以开启HTTPS
为了开启HTTPS,需要修改pod中nginx.conf的配置,在此之前,以将nginx.conf以configmap的方式应用到Pod中。
kubectl edit configmap nginx-config
data:
nginx.conf:
server {
listen 443 ssl;
server_name www.mountainmist.work;
ssl_certificate ssl/mountainmist.work_bundle.crt;
ssl_certificate_key ssl/mountainmist.work.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
...
上面配置了服务器从/etc/nginx/ssl中读取证书与密钥文件,因此之后要将secret挂载于此。
挂载nginx-https-tls secret至pod
piVersion: apps/v1
kind: Deployment
...
spec:
containers:
- name: hexo-blog
image: nginx:1.20.1
ports:
- containerPort: 80
volumeMounts:
- name: nginx-https-tls #配置nginx.conf从/etc/nginx/ssl中读取证书和密钥文件,需要secret卷挂载于此
mountPath: /etc/nginx/ssl
...
volumes:
- name: ...
- name: nginx-https-tls
secret:
secretName: nginx-https-tls # 这里引用nginx-https-tls secret来定义卷
items:
- key: tls.crt
path: mountainmist.work_bundle.crt
- key: tls.key
path: mountainmist.work.key
测试nginx是否正在使用secret中的密钥和证书
开启流量转发将https流量转发至pod中容器的443端口
1 | kubectl port-forward nginx-hexo-blog-7b4f5b78cf-k558r 8443:443 |
使用curl向服务端发送一个请求,若配置正确,则服务端响应的证书会与自己之前生成的证书相匹配
1 | [root@VM-20-9-centos ~]# curl https://localhost:8443 -k -v |
secret卷存储于内存
可以看到使用的是tmpfs,存储在secret中的数据并不会写入磁盘,这样就无法被窃取。
1 | [root@VM-20-9-centos ~]# kubectl exec nginx-hexo-blog-7b4f5b78cf-k558r -- mount |grep ssl |
通过环境变量暴露secret
除了卷之外,secret的独立条目可以作为环境变量使用,就像configmap一样,若想将secret中的键foo暴露为环境变量FOO_SECRET,需要在容器定义中添加如下片段:
1 | spec: |
1 | [root@VM-20-9-centos nginx]# kubectl exec nginx-hexo-blog-798d89bfd4-ntqkj -- env| grep USER_SECRET |
kubernetes允许通过环境变量暴露secret,然而此特性并不推荐。程序会在错误报告是转储环境变量,或是启动时打印在日志中,这便无意中暴露了secret的信息。此外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制启动应用,我们并不知道它使用敏感数据做了什么。
由于敏感数据可能在无意中被暴露,通过环境变量暴露secret给容器之前需要三思。确保安全性,请将secret始终以secret卷的方式暴露