美文网首页鲸落消零派k8s in action实践笔记
7.5 使用Secret给容器传递敏感数据

7.5 使用Secret给容器传递敏感数据

作者: 众神开挂 | 来源:发表于2021-07-26 16:33 被阅读0次

    7.5 使用Secret给容器传递敏感数据

    到目前为止传递给容器的所有信息都是比较常规的非敏感数据。然而正如本章开头提到的,配置通常会包含一些敏感数据,如证书和私钥,需要确保其安全性。

    7.5.1 介绍Secret

    为了存储与分发此类信息,Kubernetes提供了一种称为Secret的单独资源对象。Secret结构与ConfigMap类似,均是键/值对的映射。Secret的使用方法也与ConfigMap相同,可以

    • 将Secret条目作为环境变量传递给容器
    • 将Secret条目暴露为卷中的文件

    Kubernetes通过仅仅将Secret分发到需要访问Secret的pod所在的机器节点来保障其安全性。另外,Secret只会存储在节点的内存中,永不写入物理存储,这样从节点上删除Secret时就不需要擦除磁盘了。

    对于主节点本身(尤其是etcd),Secret通常以非加密形式存储,这就需要保障主节点的安全从而确保存储在Secret中的敏感数据的安全性。这种保障不仅仅是对etcd存储的安全性保障,同样包括防止未授权用户对API服务器的访问,这是因为任何人都能通过创建pod并将Secret挂载来获得此类敏感数据。从Kubernetes 1.7开始,etcd会以加密形式存储Secret,某种程度提高了系统的安全性。正因为如此,从Secret与ConfigMap中做出正确选择是势在必行的,选择依据相对简单:

    • 采用ConfigMap存储非敏感的文本配置数据。
    • 采用Secret存储天生敏感的数据,通过键来引用。如果一个配置文件同时包含敏感与非敏感数据,该文件应该被存储在Secret中。

    第5章中已经使用过Secret以存储Ingress资源的TLS证书。接下来将更深入地探讨Secret的细节。

    7.5.2 默认令牌Secret介绍

    首先来分析一种默认被挂载至所有容器的Secret,对任意一个pod使用命令kubectl describe pod,输出往往包含如下信息:

    Volume:
      kube-api-access-r8tkm:
        Type:                    Projected (a volume that contains injected data from multiple sources)
        TokenExpirationSeconds:  3607
        ConfigMapName:           kube-root-ca.crt
        ConfigMapOptional:       <nil>
        DownwardAPI:             true
    

    每个pod都会被自动挂载上一个Projected卷,这个卷引用的是前面 kubectl describe 输出中的一个叫作kube-api-access-r8tkm的Secret。

    dkube-api-access 被自动创建且对应的卷被自动挂载到每个pod上

    由于Secret也是资源对象,因此可以通过 kubectl get secrets 命令从Secret列表中找到这个 default-token Secret:

    $ kubectl get secrets
    NAME                  TYPE                                  DATA   AGE
    default-token-kwqxk   kubernetes.io/service-account-token   3      2d12h
    

    同样可以使用 kubectl describe 多了解一下这个Secret,如下面的代码清单所示。

    代码清单7.20 描述一个Secret

    $ kubectl describe secrets default-token-kwqxk
    

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

    kubectl describe pod 命令会显示secret卷被挂载的位置:

    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-r8tkm (ro)
    

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

    我们已经说过Secret类似于ConfigMap,由于该Secret包含三个条目,可通过 kubectl exec 观察到被secret卷挂载的文件夹下包含三个文件:

    $ kubectl exec fortune-configmap-volume -c web-server -- ls /var/run/secrets/kubernetes.io/serviceaccount
    ca.crt
    namespace
    token
    

    下一章中将会看到应用程序是如何使用这些文件来访问API服务器的。

    7.5.3 创建Secret

    现在你将创建自己地小型Secret。改进fortune-serving的Nginx容器的配置,使其能够服务于HTTPS流量。你需要创建私钥和证书,由于需要确保私钥的安全性,可将其与证书同时存入Secret。

    首先在本地机器上生成证书与私钥文件,当然也可以直接使用本书代码归档中的相应文件(fortune-https文件夹下的证书与密钥文件):

    参考第五章的 5.4.4 配置Ingress处理TLS传输

    现在为了帮助你更好地理解Secret,额外创建一个内容为字符串bar的虚拟文件foo。过会儿你就会理解为何要这样做:

    $ echo bar > foo
    

    现在使用kubectl create secret命令由这三个文件创建Secret:

    $ kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
    

    与创建ConfigMap的过程类似,这里创建了一个名为 fortune-httpsgeneric Secret ,它包含有两个条目:https.keyhttps.cert ,分别对应于两个同名文件的内容。如前所述,同样可以用 --from-file=fortune-https 囊括整个文件夹中的所有文件,替代单独指定每个文件的创建方式。

    注意 这里创建了一个 generic Secret ,在此之前你可能在第5章通过 kubectl create secret tls 创建过一个 tls Secret 。两种方式创建的Secret的条目名称不同。

    7.5.4 对比ConfigMap与Secret

    Secret与ConfigMap仍有比较大的差别,这也是为何Kubernetes开发者们在支持了Secret一段时间之后仍会选择创建ConfigMap。创建的Secret的YAML格式定义如下面的代码清单所示。

    代码清单7.21 Secret的YAML格式定义

    $ kubectl get secret fortune-https -o yaml
    apiVersion: v1
    data:
      foo: YmFyCg==
      https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ------------------------------------------------------------------------------------------OXURSSng1ZkduWDl1S0ZsNTY3VkhOd09KTkIKVnZQN=MzB0UUw4ckFML21kUT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
      https.key: LS0tLS1CRUdJTiBSU0EgUFJJV-----------------------------------------------------------------------------------------------3phR1k5dHZDMnZPMnpPRnNkM0piR2x2UytGSkVZdkhkTmdhUmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
    kind: Secret
    metadata:
      creationTimestamp: "2021-07-11T13:16:33Z"
      name: fortune-https
      namespace: custom
      resourceVersion: "144112"
      uid: 4f5ee1ac-eeab-4ead-a483-0f28e427cebb
    type: Opaque
    

    将其与之前创建的ConfigMap的YAML格式定义做对比:

    代码清单7.22 Config的YAML格式定义

    $ kubectl get configmap fortune-config -o yaml
    
    

    注意到两者的区别了吗?Secret条目的内容会被以Base64格式编码,而ConfigMap直接以纯文本展示。这种区别导致在处理YAML和JSON格式的Secret时会稍许有些麻烦,需要在设置和读取相关条目时对内容进行编解码。

    为二进制数据创建Secret

    采用Base64编码的原因很简单。Secret的条目可以涵盖二进制数据,而不仅仅是纯文本。Base64编码可以将二进制数据转换为纯文本,以YAML或JSON格式展示。

    提示 Secret甚至可以被用来存储非敏感二进制数据。不过值得注意的是,Secret的大小限于1MB。

    stringData字段介绍

    由于并非所有的敏感数据都是二进制形式,Kubernetes允许通过Secret的stringData字段设置条目的纯文本值,如下面的代码清单所示。

    代码清单7.23 通过stringData字段向Secret添加纯文本条目值

    apiVersion: v1
    stringData:   #stringData用来设置非二进制数据
      foo:plain text #不被Base64编码
    data:
      foo: YmFyCg==
      https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ------------------------------------------------------------------------------------------OXURSSng1ZkduWDl1S0ZsNTY3VkhOd09KTkIKVnZQN=MzB0UUw4ckFML21kUT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
      https.key: LS0tLS1CRUdJTiBSU0EgUFJJV-----------------------------------------------------------------------------------------------3phR1k5dHZDMnZPMnpPRnNkM0piR2x2UytGSkVZdkhkTmdhUmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
    kind: Secret
    metadata:
      creationTimestamp: "2021-07-11T13:16:33Z"
      name: fortune-https
      namespace: custom
      resourceVersion: "144112"
      uid: 4f5ee1ac-eeab-4ead-a483-0f28e427cebb
    type: Opaque
    
    

    stringData字段是只写的(注意:是只写,非只读),可以被用来设置条目值。通过 kubectl get -o yaml 获取Secret的YAML格式定义时,不会显示stringData字段。相反,stringData字段中的所有条目(如上面示例中的foo条目)会被Base64编码之后展示在data字段下。

    在pod中读取Secret条目

    通过secret卷将Secret暴露给容器之后,Secret条目的值会被解码并以真实形式(纯文本或二进制)写入对应的文件。通过环境变量暴露Secret条目亦是如此。在这两种情况下,应用程序均无须主动解码,可直接读取文件内容或者查找环境变量。

    7.5.5 在pod中使用Secret

    fortune-https Secret 已经包含了证书与密钥文件,接下来需要做的是配置Nginx服务器去使用它们。

    修改 fortune-config ConfigMap以开启HTTPS

    为了开启HTTPS,需要再次修改这个ConfigMap对应的配置条目:

    $ kubectl edit configmap fortune-config
    
    

    文本编辑器打开后,修改条目my-nginx-config.con的内容,如下面的代码清单所示。

    代码清单7.24 修改 fortune-config ConfigMap的数据

    apiVersion: v1
    data:
      my-nginx-config.conf: |
        server {
            listen              80;
            listen              443 ssl; 
            server_name         www.kubia-example.com;
            ssl_certificate     certs/https.crt;  #相对位置
            ssl_certificate_key certs/https.key; 
            ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
            ssl_ciphers         HIGH:!aNULL:!MD5;
            location / {
                root   /usr/share/nginx/html;
                index  index.html index.htm;
            }
    
        }
      sleep-interval: |
        25
    kind: ConfigMap
    metadata:
      creationTimestamp: "2021-07-11T11:39:04Z"
      name: fortune-config
      namespace: custom
      resourceVersion: "141957"
      uid: a0d46cf3-8d29-44f9-bfac-1e077e75743e
    
    

    上面配置了服务器从 /etc/nginx/certs 中读取证书与密钥文件,因此之后需要将secret卷挂载于此。

    挂载fortune-secret至pod

    接下来需要创建一个新的fortune-https pod,将含有证书与密钥的secret卷挂载至pod中的web-server容器,如下面的代码清单所示。

    代码清单7.25 fortune-https pod的YAML格式定义:fortune-pod-https.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: fortune-https
    spec:
      containers:
      - image: luksa/fortune:env
        name: html-generator
        env:
        - name: INTERVAL
          valueFrom: 
            configMapKeyRef:
              name: fortune-config
              key: sleep-interval
        volumeMounts:
        - name: html
          mountPath: /var/htdocs
      - image: nginx:alpine
        name: web-server
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
          readOnly: true
        - name: config
          mountPath: /etc/nginx/conf.d
          readOnly: true
        - name: certs  #配置nginx 从 /etc/nginx/certs 中读取证书和密钥.需要将secret卷挂载于此
          mountPath: /etc/nginx/certs/
          readOnly: true
        ports:
        - containerPort: 80
        - containerPort: 443
      volumes:
      - name: html
        emptyDir: {}
      - name: config
        configMap:
          name: fortune-config
          items:
          - key: my-nginx-config.conf
            path: https.conf
      - name: certs  #这里引用fortune-https Secret来定义secret卷
        secret:
          secretName: fortune-https
    
    

    注意 与configMap卷相同,secret卷同样支持通过defaultModes属性指定卷中文件的默认权限。

    测试Nginx是否正使用Secret中的证书与密钥

    pod运行之后,开启端口转发隧道将HTTPS流量转发至pod的443端口,并用curl向服务器发送请求:

    $ kubectl port-forward fortune-https 8443:443 &
    Forwarding from 127.0.0.1:8443 -> 443
    
    $ curl https://localhost:8443 -k
    Handling connection for 8443
    You have many friends and very few living enemies.
    
    

    若服务器配置正确,会得到一个响应,检查响应中服务器证书是否与之前生成的证书匹配。curl命令添加选项-v开启详细日志,如下面的代码清单所示。

    $ curl https://localhost:8443 -k -v
    * About to connect() to localhost port 8443 (#0)
    *   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8443 (#0)
    * Initializing NSS with certpath: sql:/etc/pki/nssdb
    Handling connection for 8443
    * skipping SSL peer certificate verification
    * SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    * Server certificate:
    *   subject: CN=www.kubia-example.com
    *   start date: Feb 28 07:24:46 2021 GMT
    *   expire date: Feb 26 07:24:46 2031 GMT
    *   common name: www.kubia-example.com
    *   issuer: CN=www.kubia-example.com
    > GET / HTTP/1.1
    > User-Agent: curl/7.29.0
    > Host: localhost:8443
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Server: nginx/1.19.6
    < Date: Sun, 28 Feb 2021 07:36:49 GMT
    < Content-Type: text/html
    < Content-Length: 64
    < Last-Modified: Sun, 28 Feb 2021 07:36:37 GMT
    < Connection: keep-alive
    < ETag: "603b4805-40"
    < Accept-Ranges: bytes
    < 
    You are fighting for survival in your own sweet and gentle way.
    * Connection #0 to host localhost left intact
    
    

    Secret卷存储于内存

    通过挂载secret卷至文件夹 /etc/nginx/certs 将证书与私钥成功传递给容器。secret卷采用内存文件系统列出容器的挂载点,如下面的代码清单所示。

    $ kubectl exec fortune-https -c web-server -- mount | grep certs
    tmpfs on /etc/nginx/certs type tmpfs (ro,relatime)
    
    

    由于使用的是tmpfs,存储在Secret中的数据不会写入磁盘,这样就无法被窃取。

    通过环境变量暴露Secret条目

    除卷之外,Secret的独立条目可作为环境变量被暴露,就像ConfigMap中 sleep-interval 条目做的那样。举个例子,若想将Secret中的键foo暴露为环境变量 FOO_SECRET ,需要在容器定义中添加如下片段。

    代码清单7.27 Secret条目暴露为环境变量

    apiVersion: v1
    kind: Pod
    metadata:
      name: env-single-secret
    spec:
      containers:
      - name: envars-test-container
        image: nginx
        env:
        - name: SECRET_USERNAME
          valueFrom:
            secretKeyRef: #通过secret条目设置环境变量
              name: fortune-https #secret的键
              key: foo #secret的名称
    
    

    上面片段与设置INTERVAL环境变量的基本一致,除了这里是使用secretKeyRef字段来引用Secret,而非configMapKeyRef,后者用以引用ConfigMap。

    Kubernetes允许通过环境变量暴露Secret,然而此特性的使用往往不是一个好主意。应用程序通常会在错误报告时转储环境变量,或者是启动时打印在应用日志中,无意中暴露了Secret信息。另外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制程序启动应用,你并不知道它使用敏感数据做了什么。

    提示 由于敏感数据可能在无意中被暴露,通过环境变量暴露Secret给容器之前请再三思考。为了确保安全性,请始终采用secret卷的方式暴露Secret。

    了解镜像拉取Secret

    你已经学会了如何传递Secret给应用程序并使用它们包含的数据。Kubernetes自身在有些时候希望我们能够传递证书给它,比如从某个私有镜像仓库拉取镜像时。这一点同样需通过Secret来做到。

    到目前为止所使用的容器镜像均存储在公共仓库,从上面拉取镜像时无须任何特殊的证书。不过大部分组织机构不希望它们的镜像开放给所有人,因此会使用私有镜像仓库。部署一个pod时,如果容器镜像位于私有仓库,Kubernetes需拥有拉取镜像所需的证书。让我们看一下该怎么做。

    在Docker Hub上使用私有镜像仓库

    Docker Hub除了是一个公共镜像仓库,还支持在上面创建私有仓库。通过浏览器登录 https://hub.docker.com ,找到对应的镜像仓库,勾选指定的复选框,将仓库标记为私有。

    运行一个镜像来源于私有仓库的pod时,需要做以下两件事:

    • 创建包含Docker镜像仓库证书的Secret。
    • pod定义中的imagePullSecrets字段引用该Secret。

    创建用于Docker镜像仓库鉴权的Secret

    创建一个包含Docker镜像仓库鉴权证书的Secret与7.5.3节中创建generic Secret并没有什么不同。同样使用kubectl create secret命令,仅仅是类型与参数选项的不同:

    $ kubectl create secret docker-registry mydockerhubsecret \
      --docker-username=myusername --docker-password=mypassword \
      --docker-email=my.email@provider.com
    
    

    这里创建了一个docker-registry类型的 mydockerhubsecret Secret,创建时需指定Docker Hub的用户名、密码以及邮箱。通过kubectl describe观察新建Secret的内容时会发现仅有一个条目.dockercfg,相当于用户主目录下的.dockercfg文件。该文件通常在运行docker login命令时由Docker自动创建。

    在pod定义中使用docker-registry Secret

    为了Kubernetes从私有镜像仓库拉取镜像时能够使用Secret,需要在pod定义中指定docker-registry Secret的名称,如下面的代码清单所示。

    代码清单7.28 指定镜像拉取Secret的pod定义:pod-with-private-image.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: private-pod
    spec:
      imagePullSecrets:  #能够从私有镜像仓库中拉取镜像
      - name: mydockerhubsecret
      containers:
      - image: username/private:tag
        name: main
    
    

    上述pod定义中,字段imagePullSecrets引用了 mydockerhubsecret Secret。建议你尝试一下这个特性,因为很可能在不久之后就会与私有镜像打交道。

    不需要为每个pod指定镜像拉取Secret

    假设某系统中通常运行大量pod,你可能会好奇是否需要为每个pod都添加相同的镜像拉取Secret。幸运的是,情况并非如此。第12章中将会学习到如何通过添加Secret至ServiceAccount使所有pod都能自动添加上镜像拉取Secret。

    相关文章

      网友评论

        本文标题:7.5 使用Secret给容器传递敏感数据

        本文链接:https://www.haomeiwen.com/subject/qdukmltx.html