貔貅云原生

貔貅云原生

Secret

67
0
0
2023-12-05
Secret

Secret

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

Secret主要使用的有以下几种类型:

  • Opaque:base64 编码格式的 Secret,用来存储密码、密钥等;但数据也可以通过 base64 –decode 解码得到原始数据,所有加密性很弱。

  • kubernetes.io/dockercfg: ~/.dockercfg 文件的序列化形式

  • kubernetes.io/dockerconfigjson:用来存储私有registry的认证信息,~/.docker/config.json文件的序列化形式

  • kubernetes.io/service-account-token:用于 ServiceAccount, ServiceAccount 创建时 Kubernetes 会默认创建一个对应的 Secret 对象,Pod 如果使用了 ServiceAccount,对应的 Secret 会自动挂载到 Pod 目录 /run/secrets/kubernetes.io/serviceaccount

  • kubernetes.io/ssh-auth:用于 SSH 身份认证的凭据

  • kubernetes.io/basic-auth:用于基本身份认证的凭据

  • bootstrap.kubernetes.io/token:用于节点接入集群的校验的 Secret

Opaque Secret

Secret 资源包含2个键值对: data 和 stringData,data 字段用来存储 base64 编码的任意数据,提供 stringData 字段是为了方便,它允许 Secret 使用未编码的字符串。 data 和 stringData 的键必须由字母、数字、-,_ 或 . 组成。

比如我们来创建一个用户名为 admin,密码为 admin321 的 Secret 对象,首先我们需要先把用户名和密码做 base64 编码:

➜  ~ echo -n "admin" | base64
YWRtaW4=
➜  ~ echo -n "admin321" | base64
YWRtaW4zMjE=

然后我们就可以利用上面编码过后的数据来编写一个 YAML 文件:(secret-demo.yaml)

# cat secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: YWRtaW4zMjE=

进行 apply 操作,后查看 secret 信息

[root@kube01 secret]# kubectl apply -f secret-demo.yaml
secret/mysecret created

[root@kube01 secret]# kubectl describe secret mysecret
Name:         mysecret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

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

我们可以看到 describe 看不到密钥信息,需要通过 get -o yaml 显示

[root@kube01 secret]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
  password: YWRtaW4zMjE=
  username: YWRtaW4=
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"password":"YWRtaW4zMjE=","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"type":"Opaque"}
  creationTimestamp: "2023-06-28T02:56:55Z"
  name: mysecret
  namespace: default
  resourceVersion: "4161717"
  uid: bdea7a3f-0b8e-4d2f-a143-fdbf61f6d709
type: Opaque

对于某些场景,你可能希望使用 stringData 字段,这字段可以将一个非 base64 编码的字符串直接放入 Secret 中, 当创建或更新该 Secret 时,此字段将被编码。

比如当我们部署应用时,使用 Secret 存储配置文件, 你希望在部署过程中,填入部分内容到该配置文件。例如,如果你的应用程序使用以下配置文件:

apiUrl: "https://my.api.com/api/v1"
username: "<user>"
password: "<password>"

那么我们就可以使用以下定义将其存储在 Secret 中:

# cat stringData.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  config.yaml: |
    apiUrl: "https://my.api.com/api/v1"
    username: <user>
    password: <password>

查看这个 secret 会发现config.yaml 文件内容已经被 base64 编码转换

[root@kube01 secret]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
  config.yaml: YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IDx1c2VyPgpwYXNzd29yZDogPHBhc3N3b3JkPgo=
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"stringData":{"config.yaml":"apiUrl: \"https://my.api.com/api/v1\"\nusername: \u003cuser\u003e\npassword: \u003cpassword\u003e\n"},"type":"Opaque"}
  creationTimestamp: "2023-06-28T02:56:55Z"
  name: mysecret
  namespace: default
  resourceVersion: "4162380"
  uid: bdea7a3f-0b8e-4d2f-a143-fdbf61f6d709
type: Opaque

创建好 Secret对象后,有两种方式来使用它:

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

环境变量

以环境变量形式使用

# cat env-secret-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secret1-pod
spec:
  containers:
  - name: secret1
    image: busybox
    command: [ "/bin/sh", "-c", "env" ]
    env:
    - name: USERNAME
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: username
    - name: PASSWORD
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: password

主要需要注意的是上面环境变量中定义的 secretKeyRef 字段,和我们前文的 configMapKeyRef 类似,一个是从 Secret 对象中获取,一个是从 ConfigMap 对象中获取,创建上面的 Pod:

[root@kube01 secret]# kubectl apply -f env-secret-pod.yaml
pod/secret1-pod created

[root@kube01 secret]# kubectl logs -f secret1-pod
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.254.0.1:443
HOSTNAME=secret1-pod
SHLVL=1
HOME=/root
USERNAME=admin
KUBERNETES_PORT_443_TCP_ADDR=10.254.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP=tcp://10.254.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_HOST=10.254.0.1
PWD=/
PASSWORD=admin321

可以发现使用环境变量的形式已经被读取到

Volume 挂载

以 volume 挂载形式使用

# cat volume-secret-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secret2-pod
spec:
  containers:
  - name: secret2
    image: busybox
    command: ["/bin/sh", "-c", "ls /etc/secrets"]
    volumeMounts:
    - name: secrets
      mountPath: /etc/secrets
  volumes:
  - name: secrets
    secret:
     secretName: mysecret

通过 apply 后查看 log 日志,发现挂载的两个环境变量成了独立的文件,当然如果想要挂载到指定的文件上面,是不是也可以使用上一节课的方法:在 secretName 下面添加 items 指定 key 和 path,这个大家可以参考上节课 ConfigMap 中的方法去测试下。

[root@kube01 secret]# kubectl apply -f volume-secret-pod.yaml
pod/secret2-pod created

[root@kube01 secret]# kubectl logs -f secret2-pod
password
username

kubernetes.io/dockerconfigjson

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

kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL

除了上面这种方法之外,我们也可以通过指定文件的方式来创建镜像仓库认证信息,需要注意对应的 KEY 和 TYPE:

kubectl create secret generic myregistry --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson

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

apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
  - name: foo
    image: 192.168.1.100:5000/test:v1
  imagePullSecrets:  # 此处是给 kubelet 使用的
  - name: myregistry

ImagePullSecrets 与 Secrets 不同,因为 Secrets 可以挂载到 Pod 中,但是 ImagePullSecrets 只能由 Kubelet 访问。

kubernetes.io/basic-auth

该类型用来存放用于基本身份认证所需的凭据信息,使用这种 Secret 类型时,Secret 的 data 字段(不一定)必须包含以下两个键(相当于是约定俗成的一个规定):

  • username: 用于身份认证的用户名
  • password: 用于身份认证的密码或令牌

以上两个键的键值都是 base64 编码的字符串。 然你也可以在创建 Secret 时使用 stringData 字段来提供明文形式的内容。下面的 YAML 是基本身份认证 Secret 的一个示例清单:

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: admin321

提供基本身份认证类型的 Secret 仅仅是出于用户方便性考虑,我们也可以使用 Opaque 类型来保存用于基本身份认证的凭据,不过使用内置的 Secret 类型的有助于对凭据格式进行统一处理

kubernetes.io/ssh-auth

该类型用来存放 SSH 身份认证中所需要的凭据,使用这种 Secret 类型时,你就不一定必须在其 data(或 stringData)字段中提供一个 ssh-privatekey 键值对,作为要使用的 SSH 凭据。

apiVersion: v1
kind: Secret
metadata:
  name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb/Y ...

同样提供 SSH 身份认证类型的 Secret 也仅仅是出于用户方便性考虑,我们也可以使用 Opaque 类型来保存用于 SSH 身份认证的凭据,只是使用内置的 Secret 类型的有助于对凭据格式进行统一处理。

kubernetes.io/tls

该类型用来存放证书及其相关密钥(通常用在 TLS 场合)。此类数据主要提供给 Ingress 资源,用以校验 TLS 链接,当使用此类型的 Secret 时,Secret 配置中的 data (或 stringData)字段必须包含 tls.key 和 tls.crt主键。下面的 YAML 包含一个 TLS Secret 的配置示例:

apiVersion: v1
kind: Secret
metadata:
  name: secret-tls
type: kubernetes.io/tls
data:
  tls.crt: |
        MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
  tls.key: |
        MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...

提供 TLS 类型的 Secret 仅仅是出于用户方便性考虑,我们也可以使用 Opaque 类型来保存用于 TLS 服务器与/或客户端的凭据。不过,使用内置的 Secret 类型的有助于对凭据格式进行统一化处理。当使用 kubectl 来创建 TLS Secret 时,我们可以像下面的例子一样使用 tls 子命令:

kubectl create secret tls my-tls-secret \
  --cert=path/to/cert/file \
  --key=path/to/key/file

需要注意的是用于 --cert 的公钥证书必须是 .PEM 编码的 (Base64 编码的 DER 格式),且与 --key 所给定的私钥匹配,私钥必须是通常所说的 PEM 私钥格式,且未加密。对这两个文件而言,PEM 格式数据的第一行和最后一行(例如,证书所对应的 --------BEGIN CERTIFICATE----- 和 -------END CERTIFICATE----)都不会包含在其中。

kubernetes.io/service-account-token

另外一种 Secret 类型就是 kubernetes.io/service-account-token,用于被 ServiceAccount 引用。ServiceAccout 创建时 Kubernetes 会默认创建对应的 Secret,如下所示我们随意创建一个 Pod:

➜  ~ kubectl run secret-pod3 --image nginx:1.7.9
deployment.apps "secret-pod3" created
➜  ~ kubectl get pods
NAME                           READY     STATUS    RESTARTS   AGE
...
secret-pod3-78c8c76db8-7zmqm   1/1       Running   0          13s
...

我们可以直接查看这个 Pod 的详细信息:

    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-lvhfb
      readOnly: true
  ......
  serviceAccount: default
  serviceAccountName: default
  volumes:
  - name: kube-api-access-lvhfb
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

当创建 Pod 的时候,如果没有指定 ServiceAccount,Pod 则会使用命名空间中名为 default 的 ServiceAccount,上面我们可以看到 spec.serviceAccountName 字段已经被自动设置了。

可以看到这里通过一个 projected 类型的 Volume 挂载到了容器的 /var/run/secrets/kubernetes.io/serviceaccount 的目录中,projected 类型的 Volume 可以同时挂载多个来源的数据,这里我们挂载了一个 downwardAPI 来获取 namespace,通过 ConfigMap 来获取 ca.crt 证书,然后还有一个 serviceAccountToken 类型的数据源。

前面我们也提到了默认情况下当前 namespace 下面的 Pod 会默认使用 default 这个 ServiceAccount,对应的 Secret 会自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/ 目录中,这样我们就可以在 Pod 里面获取到用于身份认证的信息了。

我们可以使用自动挂载给 Pod 的 ServiceAccount 凭据访问 API,我们也可以通过在 ServiceAccount 上设置 automountServiceAccountToken: false 来实现不给 ServiceAccount 自动挂载 API 凭据:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-robot
automountServiceAccountToken: false
...

此外也可以选择不给特定 Pod 自动挂载 API 凭据:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  serviceAccountName: build-robot
  automountServiceAccountToken: false
  ...

如果 Pod 和 ServiceAccount 都指定了 automountServiceAccountToken 值,则 Pod 的 spec 优先于 ServiceAccount。

Secret vs ConfigMap

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

相同点

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

不同点

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

同样 Secret 文件大小限制为 1MB(ETCD 的要求);Secret 虽然采用 Base64 编码,但是我们还是可以很方便解码获取到原始信息,所以对于非常重要的数据还是需要慎重考虑,可以考虑使用 Vault 来进行加密管理。