Kubernetes-Secret

張鐵牛發表於2022-01-25

1. 簡介

Secret 是一種包含少量敏感資訊例如密碼、令牌或金鑰的物件。 這樣的資訊可能會被放在 Pod 規約中或者映象中。 使用 Secret 意味著你不需要在應用程式程式碼中包含機密資料。

由於建立 Secret 可以獨立於使用它們的 Pod, 因此在建立、檢視和編輯 Pod 的工作流程中暴露 Secret(及其資料)的風險較小。 Kubernetes 和在叢集中執行的應用程式也可以對 Secret 採取額外的預防措施, 例如避免將機密資料寫入非易失性儲存。

Secret 類似於 ConfigMap 但專門用於儲存機密資料。

2. 4種建立方式

$ kubectl create secret -h
Create a secret using specified subcommand.

Available Commands:
  docker-registry 建立一個給 Docker registry 使用的 secret
  generic         從本地 file, directory 或者 literal value 建立一個 secret
  tls             建立一個 TLS secret

2.1 通過key-value建立

$ kubectl create secret generic secret-test-1 --from-literal=username=zhangtieniu --from-literal=password=123456

還有種簡單的建立方式建立此種型別的ConfigMap

$ kubectl create secret generic secret-test-1 --from-env-file=./secret-env.txt

2.2 通過配置檔案建立

$ kubectl create secret generic ssh-key-secret \
   --from-file=ssh-privatekey=./.ssh/id_rsa \
   --from-file=ssh-publickey=./.ssh/id_rsa.pub 

2.3 通過配置檔案的目錄建立

$ kubectl create secret generic ssh-key-secret   --from-file=./

2.4 直接編寫Secret檔案

在建立 Secret 編寫配置檔案時,你可以設定 data 與/或 stringData 欄位。 datastringData 欄位都是可選的。

data 欄位中所有鍵值都必須是 base64 編碼的字串

如果不希望執行這種 base64 字串的轉換操作,你可以選擇設定 stringData 欄位,其中可以使用任何字串作為其取值。

kubernetes.io/basic-auth 型別用來存放用於基本身份認證所需的憑據資訊。 使用這種 Secret 型別時,Secret 的 data 欄位必須包含以下兩個鍵:

  • username: 用於身份認證的使用者名稱;
  • password: 用於身份認證的密碼或令牌。

在建立 Secret 時使用 stringData 欄位來提供明文形式的內容。 下面的 YAML 是基本身份認證 Secret 的一個示例清單:

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: zhangtieniu
  # 值必須為string型別,所以數值型別的密碼必須加引號
  password: "123456"

直接編寫secret data檔案如下

apiVersion: v1
data:
  password: MTIzNDU2
  username: emhhbmd0aWVuaXU=
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth

3. 檢視/解密secret資訊

secret資訊預設是使用base64加密的,所以可以通過base64解密來檢視secret配置的auth資訊

shell base64

加密:

base64 file

$ echo Hello World | base64
SGVsbG8gV29ybGQK

解密:

$ echo SGVsbG8gV29ybGQK | base64 -d
Hello World

4. secret type

建立 Secret 時,你可以使用 Secret 資源的 type 欄位, 或者與其等價的 kubectl 命令列引數(如果有的話)為其設定型別。 Secret 的 type 有助於對不同型別機密資料的程式設計處理。

Kubernetes 提供若干種內建的型別,用於一些常見的使用場景。 針對這些型別,Kubernetes 所執行的合法性檢查操作以及對其所實施的限制各不相同。

內建型別 用法
Opaque 使用者定義的任意資料
kubernetes.io/service-account-token 服務賬號令牌
kubernetes.io/dockercfg ~/.dockercfg 檔案的序列化形式
kubernetes.io/dockerconfigjson ~/.docker/config.json 檔案的序列化形式
kubernetes.io/basic-auth 用於基本身份認證的憑據
kubernetes.io/ssh-auth 用於 SSH 身份認證的憑據
kubernetes.io/tls 用於 TLS 客戶端或者伺服器端的資料
bootstrap.kubernetes.io/token 啟動引導令牌資料

通過為 Secret 物件的 type 欄位設定一個非空的字串值,你也可以定義並使用自己 Secret 型別。如果 type 值為空字串,則被視為 Opaque 型別。 Kubernetes 並不對型別的名稱作任何限制。不過,如果你要使用內建型別之一, 則你必須滿足為該型別所定義的所有要求。

4.1 Opaque

當 Secret 配置檔案中未作顯式設定時,預設的 Secret 型別是 Opaque。 當你使用 kubectl 來建立一個 Secret 時,你會使用 generic 子命令來標明 要建立的是一個 Opaque 型別 Secret。 例如,下面的命令會建立一個空的 Opaque 型別 Secret 物件:

$ kubectl create secret generic test-secret

4.2 dockercfg/dockerconfigjson

你可以使用下面兩種 type 值之一來建立 Secret,用以存放訪問 Docker 倉庫 來下載映象的憑據。

  • kubernetes.io/dockercfg:舊版的docker login之後會在伺服器上生成配置檔案~/.dockercfg,用來記錄docker倉庫的auth資訊
  • kubernetes.io/dockerconfigjson:新版的docker login之後會在伺服器上生成配置檔案~/.docker/config.json,用來記錄docker倉庫的auth資訊

kubernetes.io/dockercfg 是一種保留型別,用來存放 ~/.dockercfg 檔案的 序列化形式。該檔案是配置 Docker 命令列的一種老舊形式。 使用此 Secret 型別時,你需要確保 Secret 的 data 欄位中包含名為 .dockercfg 的主鍵,其對應鍵值是用 base64 編碼的 ~/.dockercfg 檔案的內容。

型別 kubernetes.io/dockerconfigjson 被設計用來儲存 JSON 資料的序列化形式, 該 JSON 也遵從 ~/.docker/config.json 檔案的格式規則,是 ~/.dockercfg 的新版本格式。 使用此 Secret 型別時,Secret 物件的 data 欄位必須包含 .dockerconfigjson 鍵,其對應鍵值是用 base64 編碼的 ~/.docker/config.json 檔案的內容。

下面是一個 kubernetes.io/dockercfg 型別 Secret 的示例:

apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockercfg
data:
  .dockercfg: |
        "<base64 encoded ~/.dockercfg file>"
apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: |
        "<base64 encoded ~/.docker/config.json file>"

當你使用清單檔案來建立這兩類 Secret 時,API 伺服器會檢查 data 欄位中是否 存在所期望的主鍵,並且驗證其中所提供的鍵值是否是合法的 JSON 資料。 不過,API 伺服器不會檢查 JSON 資料本身是否是一個合法的 Docker 配置檔案內容。

kubectl create secret docker-registry secret-tiger-docker \
  --docker-username=tiger \
  --docker-password=pass113 \
  --docker-email=tiger@acme.com

上面的命令建立一個型別為 kubernetes.io/dockerconfigjson 的 Secret。 如果你對 data 欄位中的 .dockerconfigjson 內容進行轉儲,你會得到下面的 JSON 內容,而這一內容是一個合法的 Docker 配置檔案。

{
  "auths": {
    "https://index.docker.io/v1/": {
      "username": "tiger",
      "password": "pass113",
      "email": "tiger@acme.com",
      "auth": "dGlnZXI6cGFzczExMw=="
    }
  }
}

4.3 basic-auth

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: t0p-Secret

提供基本身份認證型別的 Secret 僅僅是出於使用者方便性考慮。 你也可以使用 Opaque 型別來儲存用於基本身份認證的憑據。

不過,使用內建的 Secret 型別的有助於對憑據格式進行歸一化處理,並且 API 伺服器確實會檢查 Secret 配置中是否提供了所需要的主鍵。

4.4 ssh-auth

Kubernetes 所提供的內建型別 kubernetes.io/ssh-auth 用來存放 SSH 身份認證中 所需要的憑據。使用這種 Secret 型別時,你就必須在其 data (或 stringData) 欄位中提供一個 ssh-privatekey 鍵值對,作為要使用的 SSH 憑據。

下面的 YAML 是一個 SSH 身份認證 Secret 的配置示例:

apiVersion: v1
kind: Secret
metadata:
  name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
  # 此例中的實際資料被截斷
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb/Y ...

提供 SSH 身份認證型別的 Secret 僅僅是出於使用者方便性考慮。 你也可以使用 Opaque 型別來儲存用於 SSH 身份認證的憑據。 不過,使用內建的 Secret 型別的有助於對憑據格式進行歸一化處理,並且 API 伺服器確實會檢查 Secret 配置中是否提供了所需要的主鍵。

注意: SSH 私鑰自身無法建立 SSH 客戶端與伺服器端之間的可信連線。 需要其它方式來建立這種信任關係,以緩解“中間人(Man In The Middle)” 攻擊,例如向 ConfigMap 中新增一個 known_hosts 檔案。

4.5 tls

Kubernetes 提供一種內建的 kubernetes.io/tls Secret 型別,用來存放證書 及其相關金鑰(通常用在 TLS 場合)。 此類資料主要提供給 Ingress 資源,用以終結 TLS 連結,不過也可以用於其他 資源或者負載。當使用此型別的 Secret 時,Secret 配置中的 data (或 stringData)欄位必須包含 tls.keytls.crt 主鍵,儘管 API 伺服器 實際上並不會對每個鍵的取值作進一步的合法性檢查。

下面的 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 型別的有助於對憑據格式進行歸一化處理,並且 API 伺服器確實會檢查 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----)都不會包含在其中。

5. 2種掛載方式

5.1 通過volume方式掛載

這裡使用2.4建立的secret進行掛載

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
    imagePullPolicy: IfNotPresent
  volumes:
  - name: foo
    secret:
      secretName: secret-basic-auth

將 Secret 鍵名對映到特定路徑

我們還可以控制 Secret 鍵名在儲存卷中對映的的路徑。 你可以使用 spec.volumes[].secret.items 欄位修改每個鍵對應的目標路徑:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
    imagePullPolicy: IfNotPresent
  volumes:
  - name: foo
    secret:
      secretName: secret-basic-auth
      items:
      - key: username
        path: my-group/my-username

將會發生什麼呢:

  • username Secret 儲存在 /etc/foo/my-group/my-username 檔案中而不是 /etc/foo/username 中。
  • password Secret 沒有被對映

如果使用了 spec.volumes[].secret.items,只有在 items 中指定的鍵會被對映。 要使用 Secret 中所有鍵,就必須將它們都列在 items 欄位中。 所有列出的鍵名必須存在於相應的 Secret 中。否則,不會建立卷。

5.2 通過環境變數方式掛載

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: secret-basic-auth
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: secret-basic-auth
            key: password
    imagePullPolicy: IfNotPresent
  restartPolicy: Never

Secret 更新之後對應的環境變數不會被更新

如果某個容器已經在通過環境變數使用某 Secret,對該 Secret 的更新不會被 容器馬上看見,除非容器被重啟。有一些第三方的解決方案能夠在 Secret 發生 變化時觸發容器重啟。

6. docker-registry auth

4.2中描述的,可以使用以下的方式建立docker-registry secret

6.1 直接建立docker-registry secret

建立 Secret,命名為 regcred

kubectl create secret docker-registry regcred \
  --docker-server=<你的映象倉庫伺服器> \
  --docker-username=<你的使用者名稱> \
  --docker-password=<你的密碼> \
  --docker-email=<你的郵箱地址>

在這裡:

  • <your-registry-server> 是你的私有 Docker 倉庫全限定域名(FQDN)。 DockerHub 使用 https://index.docker.io/v1/
  • <your-name> 是你的 Docker 使用者名稱。
  • <your-pword> 是你的 Docker 密碼。
  • <your-email> 是你的 Docker 郵箱。

這樣你就成功地將叢集中的 Docker 憑證設定為名為 regcred 的 Secret。

如果已經有 Docker 憑據檔案,則可以將憑據檔案匯入為 Kubernetes Secret, 而不是執行上面的命令。 基於已有的 docker 憑據建立 secret 解釋瞭如何完成這一操作。

如果你在使用多個私有容器倉庫,這種技術將特別有用。 原因是 kubectl create secret docker-registry 建立的是僅適用於某個私有倉庫的 Secret。

說明: Pod 只能引用位於自身所在名字空間中的 Secret,因此需要針對每個名字空間 重複執行上述過程。

6.2 基於已有的 docker 憑據建立 secret

當我們直接使用 docker login 登入docker registry後,會生成~/.docker/config.json檔案用於記錄docker登入的auth資訊

我們可以直接使用該檔案來建立secret(其實在4.2中已經記錄過)

4.2 中提到了,可以直接指定file地址用來建立kubernetes.io/dockerconfigjson(官方文件中摘抄的),但是本人測試時報錯,可能是k8s版本的問題,但是以下的方式是經過測試沒問題的

apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: ewogICJhdXRocyI6IHsKICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAgICJ1c2VybmFtZSI6ICJ0aWdlciIsCiAgICAgICJwYXNzd29yZCI6ICJwYXNzMTEzIiwKICAgICAgImVtYWlsIjogInRpZ2VyQGFjbWUuY29tIiwKICAgICAgImF1dGgiOiAiZEdsblpYSTZjR0Z6Y3pFeE13PT0iCiAgICB9CiAgfQp9Cg==

.dockerconfigjson 欄位的值是 Docker 憑證的 base64 編碼值,也就是把~/.docker/config.json 轉成了base64值。

$ base64 ~/.docker/config.json
ewogICJhdXRocyI6IHsKICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAg
ICJ1c2VybmFtZSI6ICJ0aWdlciIsCiAgICAgICJwYXNzd29yZCI6ICJwYXNzMTEzIiwKICAgICAg
ImVtYWlsIjogInRpZ2VyQGFjbWUuY29tIiwKICAgICAgImF1dGgiOiAiZEdsblpYSTZjR0Z6Y3pF
eE13PT0iCiAgICB9CiAgfQp9Cg==

6.3 使用docker-registry secret

在建立 Pod 時,可以在 Pod 定義中增加 imagePullSecrets 部分來引用該 Secret,如下配置:

apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container
    image: redis
  # 指定docker-registry secret
  imagePullSecrets:
  # docker-registry secret name
  - name: regcred

7. subPath

如果我們想掛載配置檔案時,只覆蓋指定mountPath下的指定檔案(因為預設會把Secret中的資訊掛載到mountPath下,此時mountPath下只有Secret的配置檔案,mountPath下本來有的配置檔案會被清除掉),那麼需要使用subpath來指定要被覆蓋的檔案

我在volume的6.0中詳細的寫了相關的配置,感興趣的可以瞭解下。

8. 掛載的 Secret 會被自動更新

當已經儲存於卷中被使用的 Secret 被更新時,被對映的鍵也將終將被更新。 元件 kubelet 在週期性同步時檢查被掛載的 Secret 是不是最新的。 但是,它會使用其本地快取的數值作為 Secret 的當前值。

快取的型別可以使用 KubeletConfiguration 結構 中的 ConfigMapAndSecretChangeDetectionStrategy 欄位來配置。 它可以通過 watch 操作來傳播(預設),基於 TTL 來重新整理,也可以 將所有請求直接重定向到 API 伺服器。 因此,從 Secret 被更新到將新 Secret 被投射到 Pod 的那一刻的總延遲可能與 kubelet 同步週期 + 快取傳播延遲一樣長,其中快取傳播延遲取決於所選的快取型別。 對應於不同的快取型別,該延遲或者等於 watch 傳播延遲,或者等於快取的 TTL, 或者為 0。

說明: 使用 Secret 作為子路徑卷掛載的容器 不會收到 Secret 更新。

9. 不可更改的 Secret

Kubernetes 的特性 不可變的 Secret 和 ConfigMap 提供了一種可選配置, 可以設定各個 Secret 和 ConfigMap 為不可變的。 對於大量使用 Secret 的叢集(至少有成千上萬各不相同的 Secret 供 Pod 掛載), 禁止變更它們的資料有下列好處:

  • 防止意外(或非預期的)更新導致應用程式中斷
  • 通過將 Secret 標記為不可變來關閉 kube-apiserver 對其的監視,從而顯著降低 kube-apiserver 的負載,提升叢集效能。

這個特性通過 ImmutableEmphemeralVolumes 來控制,從 v1.19 開始預設啟用。 你可以通過將 Secret 的 immutable 欄位設定為 true 建立不可更改的 Secret。 例如:

apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
immutable: true