【kubernetes系列】Kubernetes之Secret凭证

概述

在另外一章里面分享了ConfigMap的使用,我们知道ConfigMap这个资源对象是Kubernetes当中非常重要的一个对象,一般情况下ConfigMap是用来存储一些非安全的配置信息,如果涉及到一些安全相关的数据的话用ConfigMap就非常不安全,因为ConfigMap是明文存储的,我们说这个时候我们就需要用到另外一个资源对象了:Secret,Secret用来保存敏感信息,例如密码、OAuth 令牌和 ssh key等等,将这些信息放在Secret中比放在Pod的定义中或者docker镜像中来说更加安全和灵活,也便于更改,分发和使用。

Secret有四种TYPE(类型):

  • generic :base64编码格式的Secret,用来存储密码、密钥、信息、证书等,类型标识符为Opaque;但数据也通过base64 –decode解码得到原始数据,所有加密性很弱。
  • docker-registry :用来存储私有docker registry的认证信息,类型标识为kubernetes.io/dockerconfigjson。
  • tls:用于为SSL通信模式存储证书和私钥文件,命令式创建类型标识为kubernetes.io/tls。
  • kubernetes.io/service-account-token:用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/var/run/secrets/kubernetes.io/serviceaccount目录中(鉴权使用)

示例

1、Opaque:

密码

方法一:

Opaque 类型的数据是一个 map 类型,要求value是base64编码格式,比如我们来创建一个用户名为 admin,密码为 Abcd@123的 Secret 对象,首先我们先把这用户名和密码做 base64 编码,如下:

[root@k8s-m1 tmp]# echo -n "admin" | base64
YWRtaW4=
[root@k8s-m1 tmp]# echo -n "Abcd@123" | base64
QWJjZEAxMjM=

然后我们就可以使用上面编码得到的数据来编写一个YML文件:secret-opaque.yml

[root@k8s-m1 tmp]# cat secret-opaque.yml 
apiVersion: v1
kind: Secret
metadata:
  name: opaquesecret
type: Opaque
data:
  username: YWRtaW4=
  password: QWJjZEAxMjM=
#创建
[root@k8s-m1 tmp]# kubectl apply  -f secret-opaque.yml 
secret/opaquesecret created

#查看
[root@k8s-m1 tmp]# kubectl get secrets 
NAME           TYPE     DATA   AGE
NAME                                  TYPE                                  DATA   AGE
default-token-glxls                   kubernetes.io/service-account-token   3      417d
opaquesecret                          Opaque                                2      18m
##其中default-token-glxls为创建集群时默认创建的secret,被serviceacount/default 引用。

#查看详情:
[root@k8s-m1 tmp]# kubectl describe secrets opaquesecret 
Name:         opaquesecret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password:  8 bytes
username:  5 bytes

从上面我们可以看到利用describe命令查看到的Data没有直接显示出来,如果想看到Data里面的详细信息,同样我们可以通过输出成YAML文件进行查看:

[root@k8s-m1 tmp]# kubectl get secrets opaquesecret  -o yaml
apiVersion: v1
data:
  password: QWJjZEAxMjM=
  username: YWRtaW4=
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"password":"QWJjZEAxMjM=","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"name":"opaquesecret","namespace":"default"},"type":"Opaque"}
  creationTimestamp: "2023-07-05T05:51:57Z"
......

通过describe可以查看编码后的值,然而通过这个值是可以反向解码得到最初的值,所以说使用secret也只能从一定程度上解决了安全问题。

[root@k8s-m1 tmp]# echo "YWRtaW4="|base64 -d
admin[root@k8s-m1 tmp]# echo "QWJjZEAxMjM="|base64 -d
Abcd@123[root@k8s-m1 tmp]#
#可以看到最初设置的账号和密码
方法二:或者通过文件直接创建,会自动编码,key就是相应的文件名,如:
[root@k8s-m1 tmp]# echo -n 'admin' > ./username.txt
[root@k8s-m1 tmp]# echo -n 'Abcd@123' > ./password.txt

[root@k8s-m1 tmp]# kubectl create secret generic opaquesecret-1 \
  --from-file=./username.txt \
  --from-file=./password.txt
secret/opaquesecret-1 created

[root@k8s-m1 tmp]# kubectl get secrets opaquesecret-1 -o yaml
apiVersion: v1
data:
  password.txt: QWJjZEAxMjM=
  username.txt: YWRtaW4=

[root@k8s-m1 tmp]# echo "QWJjZEAxMjM=" |base64 -d  

证书

Opaque类型也可以用于存放证书类型,如下通过secret创建一个dashboard使用的证书:

[root@k8s-m1 ~]# kubectl create secret generic ingress-secret --from-file=./dashboard.crt --from-file=dashboard.key=./dashboard.key -n kube-system
secret/ingress-secret created

[root@k8s-m1 ~]# kubectl get secrets -n kube-system
NAME                  TYPE                                  DATA   AGE
default-token-w64jw   kubernetes.io/service-account-token   3      38m
ingress-secret        Opaque                                2      11s

[root@k8s-m1 ~]# kubectl describe  secrets -n kube-system ingress-secret 

2、kubernetes.io/dockerconfigjson

除了上面的Opaque这种类型外,我们还可以来创建用户docker registry认证的Secret,直接使用kubectl create命令创建即可,如下:

[root@k8s-m1 ~]# kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL

#或者
[root@k8s-m1 ~]# kubectl create secret generic docker-registry     --from-file=.dockerconfigjson=./.docker/config.json     --type=kubernetes.io/dockerconfigjson 
secret/docker-registry created
#注意config.json文件是我们手动登录镜像仓库自动生成的

然后查看Secret列表:
[root@k8s-m1 ~]# kubectl get secrets
NAME TYPE DATA AGE
docker-registry kubernetes.io/dockerconfigjson 1 2m42s
myregistry kubernetes.io/dockerconfigjson 1 4m44s
注意看上面的TYPE类型,都是kubernetes.io/dockerconfigjson,同样的可以使用describe命令来查看详细信息:

[root@k8s-m1 ~]# kubectl describe secrets  myregistry 
Name:         myregistry
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/dockerconfigjson

Data
====
.dockerconfigjson:  92 bytes

同样的可以看到Data区域没有直接展示出来,如果想查看的话可以使用-o yaml来输出展示出来:

[root@k8s-m1 ~]# kubectl describe secrets  myregistry 
Name:         myregistry
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/dockerconfigjson

Data
====
.dockerconfigjson:  92 bytes
[root@k8s-m1 ~]# kubectl get secret myregistry \-o yaml
apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyIxOTIuMTY4LjIuMTQwIjp7InVzZXJuYW1lIjoicm9vdCIsInBhc3N3b3JkIjoibWFyZ3UiLCJhdXRoIjoiY205dmREcHRZWEpuZFE9PSJ9fX0=
kind: Secret
metadata:
  creationTimestamp: "2023-07-05T06:31:29Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:.dockerconfigjson: {}
      f:type: {}
    manager: kubectl-create
    operation: Update
    time: "2023-07-05T06:31:29Z"
  name: myregistry
  namespace: default
  resourceVersion: "84486293"
  selfLink: /api/v1/namespaces/default/secrets/myregistry
  uid: 402c3738-4784-42ff-9bba-af4245d08d6c
type: kubernetes.io/dockerconfigjson

##使用base64 反解码查看
[root@k8s-m1 ~]# echo eyJhdXRocyI6eyIxOTIuMTY4LjIuMTQwIjp7InVzZXJuYW1lIjoicm9vdCIsInBhc3N3b3JkIjoibWFyZ3UiLCJhdXRoIjoiY205dmREcHRZWEpuZFE9PSJ9fX0=|base64 -d
{"auths":{"192.168.2.140":{"username":"root","password":"margu","auth":"cm9vdDptYXJndQ=="}}}[root@k8s-m1 ~]# 

如果我们需要拉取私有仓库中的docker镜像的话就需要使用到上面的myregistry这个Secret:

[root@k8s-m1 tmp]# cat  imagesecret-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: busybox
    image: 192.168.2.140/busybox:v1
  imagePullSecrets:
  - name: myregistry
#前提保证镜像仓库已经有这个镜像,且凭证正确创建 

[root@k8s-m1 tmp]# kubectl apply -f  imagesecret-pod.yml 
pod/busybox created

如上我们通过创建的Secret凭证,从私有仓库中拉取了镜像192.168.2.140/busybox:v1部署业务。

3、kubernetes.io/tls

kubernetes.io/tls用于为SSL通信模式存储证书和私钥文件,一般用于构建TLS站点(https类型的网站)。

[root@k8s-m1 tmp]# openssl genrsa -out tls.key 2048
Generating RSA private key, 2048 bit long modulus
...................................+++
.................................................................................................................................................+++
e is 65537 (0x10001)

[root@k8s-m1 tmp]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=margu.com

[root@k8s-m1 tmp]# ls -al tls.*
-rw-r--r-- 1 root root 1273 Jul  5 15:08 tls.crt
-rw-r--r-- 1 root root 1679 Jul  5 15:07 tls.key

[root@k8s-m1 tmp]# kubectl create secret tls nginx-secret --cert=tls.crt --key=tls.key -n ingress-nginx 
secret/nginx-secret created
[root@k8s-m1 tmp]# kubectl describe secrets -n ingress-nginx nginx-secret 
Name:         nginx-secret
Namespace:    ingress-nginx
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/tls
Data
====
tls.key:  1679 bytes
tls.crt:  1273 bytes
#如何引用请查看ingress章节

4、kubernetes.io/service-account-token

有些情况下,我们希望在 pod 内部访问 apiserver,获取集群的信息,甚至对集群进行改动。针对这种情况,kubernetes 提供了一种特殊的认证方式:Service Account。 Service Account 是面向 namespace 的,每个 namespace 创建的时候,kubernetes 会自动在这个 namespace 下面创建一个默认的 Service Account;并且这个 Service Account 只能访问该 namespace 的资源。Service Account 和 pod、service、deployment 一样是kubernetes 集群中的一种资源,用户也可以创建自己的 serviceaccount。

ServiceAccount 主要包含了三个内容:namespace、Token 和 CA。namespace 指定了 pod 所在的 namespace,CA 用于验证 apiserver 的证书,token 用作身份验证。它们都通过mount 的方式挂载到 pod 中,其中 token 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/token ,是 kube-controller-manager 通过–service-account-private-key-file私钥签发的token; CA 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt ,namespace 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/namespace 。

如果 token 能够通过认证,那么请求的用户名将被设置为 system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT) ,而请求的组名有两个: system:serviceaccounts 和 system:serviceaccounts:(NAMESPACE)。

API Server的service account验证过程

以kube-system namespace下的“default” service account为例,Pod的usrname全称为:system:serviceaccount:kube-system:default。有了username,那么credentials呢?就是上面提到的service-account-token中的token。API Server的验证环节支持多种身份校验方式:CA 证书认证、Token 认证、Base 认证。一旦API Server发现client发起的request使用的是service account token的方式,API Server就会自动采用signed bearer token方式进行身份校验。而request就会使用携带的service account token参与验证。该token是API Server在创建service account时用API server启动参数:–service-account-key-file=/etc/kubernetes/pki/sa.pub的值签署(sign)生成的。如果–service-account-key-file=未传入任何值,那么将默认使用–tls-private-key-file的值,即API Server的私钥。

这里我们使用一个nginx镜像来验证一下,大家想一想为什么不是呀busybox镜像来验证?当然也是可以的,但是我们就不能在command里面来验证了,因为token是需要Pod运行起来过后才会被挂载上去的,直接在command命令中去查看肯定是还没有 token 文件的。

[root@k8s-m1 tmp]#  cat test-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  namespace: default
  labels:
    name: myapp
spec:
  containers:
  - name: test-pod
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c"," sleep 3600"]

[root@k8s-m1 tmp]#  kubectl apply -f  test-pod.yml

[root@k8s-m1 tmp]# kubectl exec -it  liveness-exec-pod-1 -- /bin/sh
/ # ls  /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt     namespace  token
/ # cat /var/run/secrets/kubernetes.io/serviceaccount/token 
eyJhbGciOiJSUzI1NiIsImtpZCI6IjJMRm8zNktIWkdvaHRDZ2szX2Vyd3JFQTE5OEZ6TjNuRnUyYTFueGE3emMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tZ2x4bHMiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE4YjkxN2ZkLTYyZTAtNDViYi1hMjNkLWQ2NDQwYTAyZDJjNyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.GFkJpzoThq4C4lGgMBiNpgQiMxktaSMl0Bg2-wZYn0dNJ-hW5HFJJzRFODVkZOjo15NTiBC234qZq6xx1lCGBx-pZJRM4mjuXM93U0AJEGeOL-LyvgUJLG4ar3popNpkQxLvEMn4qpVgdiuU7oQuFT0qw70j4cjR1-jE9ew2yS_7iYiCDBUHmNBKues-hr4EvfNKDUvN45DdEWBiWH2WrNlsh9G-0S8GKYDYRByhR0CU4lPPNeCGz-VsfrEQ9PUnU-q4Fufc2nmokv5nRJuY7eWYJf67HXpTd7tPh_hXCvn1XmEqlCCH1uE9zFMFatmIWTbXtyIwWRMZePnBQ3BlVQ
/ # 

secret的调用

那么创建好Secret对象后,我们如何使用它呢,目前有两种方式来使用它:

  • 以环境变量的形式
  • 以Volume的形式挂载

环境变量

方法一:可以指定使用某些key
首先我们来测试下环境变量的方式,我们使用一个简单的busybox镜像来测试:

#创建测试yaml文件
[root@k8s-m1 tmp]# cat secret-env-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: secret-env
    image: busybox
    command: [ "/bin/sh", "-c", "sleep 3600s" ]
    env:
    - name: USERNAME
      valueFrom:
        secretKeyRef:
          name: opaquesecret
          key: username
#    - name: PASSWORD
#      valueFrom:
#        secretKeyRef:
#          name: opaquesecret
#          key: password

##部署
[root@k8s-m1 tmp]# kubectl apply  -f secret-env-pod.yml 
pod/secret-env-pod created
##检查测试
[root@k8s-m1 tmp]# kubectl exec -it secret-env-pod -- /bin/sh
/ # env |grep -Ei 'user|pass'
USERNAME=admin
#PASSWORD=Abcd@123
/ # 

说明:可以只使用部分key

方法二:一次性使用全部的key

[root@k8s-m1 tmp]# cat  secret-env-pod-all.yml 
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod-all
spec:
  containers:
    - name: secret-env-all
      image: busybox
      command: [ "/bin/sh", "-c", "sleep 3600s" ]
      envFrom:
      - secretRef:
          name: opaquesecret
  restartPolicy: Never

[root@k8s-m1 tmp]# kubectl apply  -f secret-env-pod-all.yml 
pod/secret-env-pod-all created

[root@k8s-m1 tmp]# kubectl exec -it secret-env-pod-all  -- /bin/sh
/ # env |grep -Ei 'user|pass'
username=admin
password=Abcd@123
/ # 

可以看到上面两种方法env都有对应的环境变量,第一种可以指定使用某些key,第二种直接将全部的key生效,实际使用中我们偏向第二种,都生效至于调不调用无所谓。

Volume 挂载

同样的我们用一个Pod来验证下Volume挂载

#创建测试yaml文件
[root@k8s-m1 tmp]# cat secret-volume-pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: secret-volume-pod
spec:
  containers:
  - name: secret-volume
    image: busybox
    command: ["/bin/sh", "-c", "sleep 3600s"]
    volumeMounts:
    - name: secrets
      mountPath: /etc/secrets
  volumes:
  - name: secrets
    secret:
     secretName: opaquesecret

#部署
[root@k8s-m1 tmp]# kubectl apply  -f secret-volume-pod.yml 
pod/secret-volume-pod created
#检查测试
[root@k8s-m1 tmp]# kubectl exec -it secret-volume-pod  -- /bin/sh
/ # ls /etc/secrets/
password  username
/ # cat  /etc/secrets/username 
admin/ # 
/ # cat  /etc/secrets/password 
Abcd@123/ # 
/ # 

可以看到secret把两个key挂载成了两个对应的文件。

Secret 与 ConfigMap 对比

最后我们来对比下Secret和ConfigMap这两种资源对象的异同点:

相同点:

  • key/value 的形式
  • 属于某个特定的namespace
  • 可以导出到环境变量
  • 可以通过目录/文件形式挂载
  • 通过volume挂载的配置信息均可热更新

不同点:

  • Secret 可以被ServerAccount关联
  • Secret 可以存储docker register的鉴权信息,用在ImagePullSecret 参数中,用于拉取私有仓库的镜像
  • Secret 支持Base64加密
  • Secret 分为 kubernetes.io/service-account-token、kubernetes.io/dockerconfigjson、Opaque 、kubernetes.io/tls 四种类型,而Configmap不区分类型

注意点

  • 当Pod被API Server创建时,API Server不会校验该Pod引用的Secret是否存在。
  • 一旦这个Pod被调度,则Kubelet将试着获取Secret的值。如果Secret不存在或暂时无法连接到API Server,则kubelet将按一定的时间间隔定期重试获取该Secret,并发送一个Event来解释Pod没有启动的原因。一旦Secret被Pod获取,则Kubelet将创建并Mount包含Secret的Volume。
  • 只有所有的Volume被Mount后,Pod中的Container才会被启动。
  • 在kubelet启动Pod中container后,Container中和Secret相关的Volume将不会被改变,即使Secret本身被修改了(也就是secret不支持热更新)。
  • 为了使用更新后的Secret,需要删除旧的Pod,并重新创建一个新的Pod,因此更新Secret的流程和部署一个新的Image是一样的。

更多关于kubernetes的知识分享,请前往博客主页。编写过程中,难免出现差错,敬请指出

你可能感兴趣的:(Kubernetes,kubernetes,容器,云原生)