使用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
2
3
default-token-6nw87:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-6nw87

每个pod都会被自动挂载上一个secret卷,这个卷引用的是前面kubectl describe输出中的一个叫作default-token-cfee9的Secret。由于Secret也是资源对象,因此可以通过kubectl get secrets命令从Secret列表中找到这个

1
2
3
4
5
6
7
8
9
10
11
12
13
Name:         default-token-6nw87
Namespace: default
Labels:
Annotations: kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: 70c4260e-3a97-4848-9540-aca4fd981251

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1025 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9...

可以看出这个Secret包含三个条目——ca.crt、namespace与token,包含了从pod内部安全访问Kubemetes API服务器所需的全部信息。尽管你希望做到用程序对Kubernetes的完全无感知,然而在除了直连Kubernetes别无他法的情况下你将会使用到secret卷提供的文件。
kubectl describe pod命令会显示secret卷被挂载的位置:

1
2
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-6nw87 (ro)

注意default-token Secret默认会被挂载至每个容器。可以通过设置pod定义中的automountServiceAccountToken字段为false、或者设置pod使用的服务账户中的相同字段为false来关闭这种默认行为

可以通过exec观察到被secret卷挂载的文件夹下包含三个文件

1
2
3
4
[root@VM-20-9-centos ~]# kubectl exec    nginx-hexo-blog-7b4f5b78cf-4p5hb ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token

创建Secret

创建私钥和证书,用于nginx容器 https的配置

1
2
openssl genrsa -out https.key 2048
openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.kubia-example.com

创建一个内容为字符串bar的文件foo

1
echo bar > foo

使用kubectl create secret 命令有这两个文件创建Secret

1
2
kubectl create secret generic https --from-file=https.key --from-file=https.cert --from-file=foo
secret/https created

对比configmap和Secret

Secret和configmap仍有比较大的差距。

configmap的yaml格式定义

1
2
3
4
5
6
7
8
[root@VM-20-9-centos ~]# kubectl get configmap  nginx-config -o yaml
apiVersion: v1
data:
nginx.conf: "# For more information on configuration, see:\n# * Official English
Documentation: http://nginx.org/en/docs/\n# * Official Russian Documentation:
http://nginx.org/ru/docs/\n\n\n\nerror_log /var/log/nginx/error.log;\n\n\n# Load
dynamic modules. See /usr/share/doc/nginx/README.dynamic.\ninclude /usr/share/nginx/modules/*.conf;\n\nevents
...

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
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
stringData:
username: administrator

可以看到以YAML格式展示创建的secret,username字段与创建时的条目不太相符,你可能认为这是base64加密导致的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@VM-20-9-centos k8s]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
username: YWRtaW5pc3RyYXRvcg==
kind: Secret
metadata:
creationTimestamp: "2024-06-21T03:51:16Z"
name: mysecret
namespace: default
resourceVersion: "1989013"
selfLink: /api/v1/namespaces/default/secrets/mysecret
uid: 1e7d2132-3956-43c4-b411-b51f030e243e
type: Opaque

通过base64的decode解密,可以看到解密出的信息正是创建stringdata条目时的信息,说明stringdata的条目被Base64解析到了data字段

1
2
[root@VM-20-9-centos k8s]# echo "YWRtaW5pc3RyYXRvcg=="|base64 -d 
administrator

将Secret应用到pod中

修改nginx configmap以开启HTTPS

为了开启HTTPS,需要修改pod中nginx.conf的配置,在此之前,以将nginx.conf以configmap的方式应用到Pod中。

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@VM-20-9-centos ~]# curl https://localhost:8443 -k -v 
* About to connect() to localhost port 8443 (#0)
* Trying ::1...
* Connected to localhost (::1) port 8443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* subject: CN=mountainmist.work #证书与之前创建并存储于secret中的证书相匹配
* start date: Apr 09 00:00:00 2024 GMT
* expire date: Apr 09 23:59:59 2025 GMT
* common name: mountainmist.work
* issuer: CN=TrustAsia RSA DV TLS CA G2,O="TrustAsia Technologies, Inc.",C=CN
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:8443
> Accept: */*
> ...

secret卷存储于内存

可以看到使用的是tmpfs,存储在secret中的数据并不会写入磁盘,这样就无法被窃取。

1
2
[root@VM-20-9-centos ~]# kubectl exec   nginx-hexo-blog-7b4f5b78cf-k558r -- mount |grep ssl
tmpfs on /etc/nginx/ssl type tmpfs (ro,relatime)

通过环境变量暴露secret

除了卷之外,secret的独立条目可以作为环境变量使用,就像configmap一样,若想将secret中的键foo暴露为环境变量FOO_SECRET,需要在容器定义中添加如下片段:

1
2
3
4
5
6
7
8
9
spec:
containers:
...
env:
- name: USER_SECRET #通过secret条目设置环境变量
valueFrom:
secretKeyRef:
name: mysecret # secret的名称
key: username # secret的键
1
2
[root@VM-20-9-centos nginx]# kubectl exec   nginx-hexo-blog-798d89bfd4-ntqkj  -- env| grep USER_SECRET
USER_SECRET=administrator

kubernetes允许通过环境变量暴露secret,然而此特性并不推荐。程序会在错误报告是转储环境变量,或是启动时打印在日志中,这便无意中暴露了secret的信息。此外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制启动应用,我们并不知道它使用敏感数据做了什么。

由于敏感数据可能在无意中被暴露,通过环境变量暴露secret给容器之前需要三思。确保安全性,请将secret始终以secret卷的方式暴露