kubernetes - Secrets

Kubernetes Secrets 讓你可以儲存敏感資訊, 像是密碼 OAuth tokens, 以及 ssh keys。 將機密資訊儲存在 Secret 比一字不差的存在 Pod 定義 yaml 檔或容器鏡像中安全也靈活的多。 更多資訊可以參考 Secrets design document

概述

Secret 是一個物件, 它含有小量的敏感資訊, 像是密碼, OAuth toke, 或是 key。 這些資訊也可能被放在 Pod 的 specification 或 image 當中。 使用者可以建立 secrets, 而系統也可以建立 secrets。
要使用 secret, Pod 必須 reference secret。 Secret 有兩種方式可與 Pod 一起使用。

  • 可以以檔案的形式存在於 volume, volume 可掛載到一個或多個容器
  • 當拉取鏡像時, 為 kubelet 所使用

內建的 Secretes

Service account 自動建立並且將 API 機敏資訊加到 Secrets 當中

Kubernetes 自動建立 secrets, 這些 secretes 含有存取 API 所需的機敏資訊, 以及自動的修改 Pods 來使用這些 secret
這些自動建立並且使用 API 機敏資訊可以被取消, 並且如果想要的話也可以覆蓋這些規則。 然而, 如果你想要安全的存取 API server 的話, 這個 workflow 會是比較建議的。
更多有關 service account 的資訊可以參考 ServiceAccount 文件


建立你自己的 Secrets

使用 kubectl 建立 Secret

Secrets 可以包含 Pod 存取 database 時所需要的帳密。 比方說, 資料庫連線包含的 username 以及 password。 你可以將 username 存到本機的 ./username.txt 檔案, 以及 password 存到 ./password.txt 檔案

# 建立需要的檔案, 之後的範例會用到
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt

kubectl create secret 指令會打包這些檔案到 Secret 並且在 API server 當中建立這個物件。

  • 執行指令:
    kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
  • 輸出類似:
    secret "db-user-pass" created

注意:
特殊符號像是 $, \, *, 以及 ! 會被你的 shell 轉換, 所以會需要 escape。 在大部分的 shell 當中, escape password 最簡單的方法就是用 single quote (') 把 string 包住。 舉例來說, 如果你的密碼是 S!B\*d$zDsb, 你應執行指令如下:

  • 執行指令:
    kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb'
  • 類似輸出:
    kubectl get secrets
  • 類似輸出:
    NAME                  TYPE                                  DATA      AGE
    db-user-pass Opaque 2 51s

你可以檢視 secret 的描述

  • 執行指令:

    kubectl describe secrets/db-user-pass
  • 類似輸出:

    Name:            db-user-pass
    Namespace: default
    Labels: <none>
    Annotations: <none>

    Type: Opaque

    Data
    ====
    password.txt: 12 bytes
    username.txt: 5 bytes
  • 注意: 指令 kubectl get 以及 kubectl describe 預設上會避免顯示 secret 的內容, 這是為了防止旁觀者看到內容, 或是避免內容被儲存到 terminal 的 log 中

可參考 decoding a secret 有關如何檢視 secret 內容。


手動建立 Secret

你也可以從檔案建立 Secret, 可以是 JSON 或是 YAML 格式, 然後建立這個物件。 Secret 包含兩種 maps: data 以及 stringDatadata 欄位用來儲存任意的資料, 使用 base64 加密。 stringData 讓你可以提供未加密的 string
舉例來說, 使用 data 欄位來儲存兩個 string, 將 string 如下轉為 base64

  • 執行指令
    echo -n 'admin' | base64
  • 輸出類似
    YWRtaW4=
  • 執行指令
    echo -n '1f2d1e2e67df' | base64
  • 輸出類似
    MWYyZDFlMmU2N2Rm

建立一個 Secret YAML file 如下:

apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm

然後使用 kubectl apply 來建立

  • 執行指令
    kubectl apply -f ./secret.yaml
  • 輸出類似
    secret "mysecret" created

某些情況之下, 你可能會需要使用 stringData 欄位。 這個欄位讓你可以輸入一個未經 base64 加密的 string, 然後這個 string 會在 Secret 被建立或更新時自動加密

一個例子是, 當你在部署一個應用, 這個應用使用 Secret 來儲存設定檔, 然後你想要在部署過程中載入設定檔中的部份資訊

舉例來說, 你的應用使用以下設定檔

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

你可以使用以下的定義來將這個設定檔儲存到 Secret

apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
stringData:
config.yaml: |-
apiUrl: "https://my.api.com/api/v1"
username: {{username}}
password: {{password}}

你的部署工具可以在執行 kubectl apply 之前將 {{username}} 以及 {{password}} 範本變數替換掉

stringData 欄位是只允許寫的欄位, 當你在存取 Secrets 時, 它是不會輸出的。 舉例來說, 如果你執行以下指令:

  • 執行指令:
    kubectl get secret mysecret -o yaml
  • 類似輸出:
    apiVersion: v1
    kind: Secret
    metadata:
    creationTimestamp: 2018-11-15T20:40:59Z
    name: mysecret
    namespace: default
    resourceVersion: "7225"
    uid: c280ad2e-e916-11e8-98f2-025000000001
    type: Opaque
    data:
    config.yaml: YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IHt7dXNlcm5hbWV9fQpwYXNzd29yZDoge3twYXNzd29yZH19

如果有一個欄位, 舉例來說, username, 同時指定到 data 以及 stringData, 那將會使用 stringData。 舉例來說, 以下的範例 Secret 定義:

apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
stringData:
username: administrator

結果輸出如下:

apiVersion: v1
kind: Secret
metadata:
creationTimestamp: 2018-11-15T20:46:46Z
name: mysecret
namespace: default
resourceVersion: "7579"
uid: 91460ecb-e917-11e8-98f2-025000000001
type: Opaque
data:
username: YWRtaW5pc3RyYXRvcg==

YWRtaW5pc3RyYXRvcg== 經過 base64 解密後為 administrator
data 以及 stringData 的 key 需含有 alphanumeric 符號, -, _, 或 .

注意: 有序列的 JSON 以及 YAML secret data 值會被加密成 base64 string。 在這些 string 中, Newlines 是不合法的且必須要忽略。 當在 Darwin/macOS 中使用 base64 時, 使用者須避免使用 -b 選項來分開長行。 相反的, Linux 使用者需加入 -w 0 選項 到 base64 指令, 如果 -w 不可獲得, 那可以使用 base64 | tr -d '\n'


使用 generator 建立 Secret

從 Kubernetes v1.14 開始, kubectl 支援 使用 kustomize 管理物件。 kustomize 提供了 resource Generators (資源產生器) 來建立 Secret 以及 ConfigMaps。 Kustomize generators 需被指定在 kustomization.yaml 檔案, 而這個檔案需位於一個資料夾內。 在產生 Secret 後, 你可以使用 kubectl apply 來在 API server 中建立 Secret 物件


從檔案產生 Secret

你可以定義 secretGenerator 從檔案 ./username.txt 以及 ./password.txt 產生 Secret, 如下:

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: db-user-pass
files:
- username.txt
- password.txt
EOF

Apply 含有 kustomization.yaml 檔資料夾來建立 Secret:

kubectl apply -k .

輸出類似:

secret/db-user-pass-96mffmfh4k created

經由以下指令確認 secret 已建立

kubectl get secrets

輸出類似:

NAME                             TYPE                                  DATA      AGE
db-user-pass-96mffmfh4k Opaque 2 51s
  • 輸入指令

    kubectl describe secrets/db-user-pass-96mffmfh4k
  • 類似輸出:

    Name:            db-user-pass
    Namespace: default
    Labels: <none>
    Annotations: <none>

    Type: Opaque

    Data
    ====
    password.txt: 12 bytes
    username.txt: 5 bytes

解密 Secret

Secret 可以透過 kubectl get secret 取得。 比方說, 你可以執行 以下指令來檢視已建立的 secret:

  • 執行指令

    kubectl get secret mysecret -o yaml
  • 輸出類似:

    apiVersion: v1
    kind: Secret
    metadata:
    creationTimestamp: 2016-01-22T18:41:56Z
    name: mysecret
    namespace: default
    resourceVersion: "164619"
    uid: cfee02d6-c137-11e5-8d73-42010af00002
    type: Opaque
    data:
    username: YWRtaW4=
    password: MWYyZDFlMmU2N2Rm
  • 解密 password 欄位

    echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
  • 輸出類似:

    1f2d1e2e67df

編輯 Secret

可使用以下指令編輯 Secret

  • 執行

    kubectl edit secrets mysecret
  • 這將會打開預設編輯器, 並且可以更新在 data 欄位的 base64 加密的 Secret 值:

    # 更新以下物件, # 開頭的行會被忽略
    # 若檔案為空會終止這次編輯。 若儲存檔案時有錯, 則會回應相關錯誤。

    apiVersion: v1
    data:
    username: YWRtaW4=
    password: MWYyZDFlMmU2N2Rm
    kind: Secret
    metadata:
    annotations:
    kubectl.kubernetes.io/last-applied-configuration: { ... }
    creationTimestamp: 2016-01-22T18:41:56Z
    name: mysecret
    namespace: default
    resourceVersion: "164619"
    uid: cfee02d6-c137-11e5-8d73-42010af00002
    type: Opaque

使用 Secret

Secret 可被以 data volume 的方式掛載, 或是以環境變數的方式被 Pod 中的容器使用。 Secret 亦可非直接地暴露給 Pod, 由系統的其他部分所使用。 舉例來說, 一些系統的其他部分會代表你與外部系統互動, 而 Secret 可儲存其中會使用到的機敏資訊。


以 Pod 中檔案的方式使用 Secret

在 Pod 的 volume 中使用 Secret:

  • 建立 secret 或使用現有的。 多個 Pod 可使用同一個 secret
  • 修改 Pod 定義, 在 .spec.volumes[] 下增加一個 volume。 娶個任意的名字, 讓 .spec.volumes[].secret.secretName 欄位與 Secret 物件的名稱相同
  • 增加 .spec.container[].volumeMounts[] 到需要 secret 的每個容器。 指定 .spec.container[].volumeMounts[].readOnly = true 以及 spec.container[].volumeMounts[].mountPath 到一個未使用的資料夾名稱, 你想要 secret 會出現在這個資料夾內
  • 修改鏡像或指令, 所以程式會在這個資料夾尋找檔案。 每個 secret data 中的 key 都會變成是 mountPath 下的一個檔案

以下是掛載 Secret 到 Pod volume 範例:

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

每個你想要使用的 Secret 都必須在 .spec.volumes 中被 reference
如果在 Pod 中有很多容器, 那麼每一個容器都需要各自的 volumeMounts 區塊, 但 .spec.volumes 每一個 Secret 只需要一個
你可以將多個檔案打包到一個 secret, 或使用多個 secret, 取決於你覺得方便。


投射 Secret keys 到指定的路徑

在 secret key 投射的 volume 內, 你也可以控制其路徑。 你可以使用 .spec.volumes[].secret.items 欄位來改變每個 key 的目標路徑

  • 範例
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    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 中的 key 會被投射。 如果要使用所有的 keys, 那所有的 keys 都要被列在 items 欄位下。 所有被列在 items 內的 key 必須存在於對應的 Secret 下, 否則跟 volume 不會被建立。


Secret 檔案權限

你可以為每一個 Secret key 設定存取權限。 如果你沒特別指定, 預設使用 0644。 你也可以設定一個預設權限給整個 Secret volume, 而如果有需要的話, 可以複寫個別的 key
舉例來說, 預設權限如下:

apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
defaultMode: 256

然後, 這個 secret 將會被掛載到 /etc/foo, 且所有由此 secret volume 建立的檔案權限皆為 0400
注意到, 因為 JSON 格式並不支援八進制符號, 所以使用 256 來代表 0400 權限。 如果你使用 YAML 而不是 JSON 的話, 你可以直接使用八進制來指定權限, 這樣看起來更自然。

如之前的範例, 你也可以使用 mapping 來針對不同的檔案指定不同的權限, 如下:

apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
mode: 511

在這個範例中, 檔案 /etc/foo/my-group/my-username 的權限將會是 0777。 因為 JSON 的限制, 所以你必須以十進位的方式指定
注意到, 權限的值在你待會讀到的範例中, 會以十進位方式呈現


從 volumes 中使用 Secret

在我掛載 secret volume 的容器內, secret key 以檔案的形式存在, 且其值為 base64 解密過, 存愛這些檔案內。 以下是在容器內執行指令的結果

  • 執行指令:

    ls /etc/foo/
  • 類似輸出:

    username
    password
  • 執行指令:

    cat /etc/foo/username
  • 類似輸出:

    admin
  • 執行指令

    cat /etc/foo/password
  • 類似輸出:

    1f2d1e2e67df

容器內的程式將會負責讀取這些代表 secret 的檔案


掛載的 Secret 會自動更新

當被 volume 所使用的 Secret 更新了, 投射的 key 最終也會跟著更新。 Kubelet 會週期性的確認掛載的 Secret 是最新的。 然而, Kubelet 使用本地的 cache 來取得 Secret 當前的值。 cache 的類型為 KubeletConfiguration structConfigMapAndSecretChangeDetectionStrategy 欄位所定義。 Secret 可以由 watch (預設) 傳播, 或 ttl-based, 或簡單的轉發所有 requests 到 API server。 結果就是, 從 Secret 被更新那刻開始到新的 key 被投射到 Pod 的總時間會等於 Kubelet 同步週期 + cache 傳播延遲時間, 而 cache 傳播延遲時間取決於選擇的 cache type (相當於 watch 傳播延遲時間, ttl of cache, 或 0 相對延遲)

注意: 以 subPath volume 掛載方式使用 Secret 的容器將不會收到 Secret 通知


以環境變數的方式使用 Secret

若要以 環境變數 的方式在 Pod 中使用 Secret 的話:

  • 建立一個 secret, 或使用現存的。 多個 Pod 可使用同一個 Secret
  • 修改 Pod 的定義, 在 Pod 中的每一個你希望使用 secret 值得容器內, 為每個 secret key 增加環境變數。 使用 secret key 的環境變數必須在 env[].valueFrom.secretKeyRef 欄位中載入 secret name 以及 key
  • 修改鏡像 以及/或 命令行, 讓程式使用指定的環境變數

以下為從環境變數中使用 secrets 的範例:

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

從環境變數來使用 Secret 值

在把 secret 當成環境變數來使用的容器中, secret key 以一般的環境變數方式存在, 這些環境變數的值為 base64 解密後 secret 的值。 以下是一些在容器內指令執行後的結果:

  • 執行
    echo $SECRET_USERNAME
  • 結果
    admin
  • 執行
    echo $SECRET_PASSWORD
  • 結果
    1f2d1e2e67df

使用 imagePullSecrets

imagePullSecrets 欄位唯一系列 Secret 的指向。 你可以使用 imagePullSecrets 來將含有 Docker (或其他類型) 鏡像 registry 密碼這些資料傳給 kubelet。 Kubelet 會使用這些資訊來代替 Pod 拉取私人鏡像。 更多有關 imagePullSecrets 欄位的資訊可以參考 PodSpec API


手動指定 imagePullSecret

容器鏡像文件 可以學到如何指定 ImagePullSecrets


自動附加 imagePullSecret

你可以手動建立 imagePullSecrets, 並且從 ServiceAccount 來參照。 任何由這個 ServiceAccount 所建立的 Pod, 或是預設由這個 ServiceAccount 建立的 Pod 都會將 imagePullSecrets 欄位設為此 ServiceAccount。 更多資訊可參考 將 ImagePullSecrets 加到 ServiceAccount


自動掛載手動建立的 Secret

手動建立的 secrets (比如說, 一個含有存取 GitHub 帳號 token 的 secret) 可以被自動附加到 Pods, 依據他們的 service account。 更多的資訊可以參考 使用 PodPreset 來將資訊注入 Pods



Details

限制

Secret volume 的來源會被驗證, 以確保指定的參照物件確實指向一個 Secret 物件。 因此, 在使用 Secret 到任何 Pods 之前, 需要先建立 Secret。

Secret 資源隸屬於 namespace。 Secret 只可被在同一個 namespace 下的 Pod 參照

每個 secret 最大可 1MiB。 這是為了避免建立一個很大的 Secret, 這很可能會耗盡 API server 以及 kubelet 的 memory。 然而, 如果建立很多小的 secret 一樣會吃光 memory

kubelet 只支援在 Pods 中使用 secrets 當 secret 是從 API server 獲得。 這包含任何使用 kubectl 建立的 Pods, 或間接透過 replication controller。 這不包含使用 kubelet --manifest-url flag, --config flag, 或 REST API 所建立的 Pod

除非有特別指定 optional, 否則在 Secret 位於 Pods 中被使用為環境變數之前, 需先建立。 如果參照的 secret 不存在, 那這個 Pod 將不會開始。

參照的 keys (secretKeyRef 欄位) 如果不存在, 那 Pod 一樣不會開始

如果 secret 被使用於環境變數載入 envFrom 欄位, 那被視為不合法的環境變數名稱將會被忽略, Pod 可以開始。 如果有不合法的 key 被忽略, 那麼對應的事件 InvalidVariableNames 以及訊息將會產生。 不合法的 key, 像是 :1badkey 以及 2alsobad

  • 這時如果執行:
    kubectl get events
  • 輸出類似:
    0s         0s          1         dapi-test-pod   Pod                                         Warning   InvalidEnvironmentVariableNames   kubelet, 127.0.0.1      Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.

Secret 以及 Pod 生命週期的互動

當 Pod 經由呼叫 Kubernetes API 被建立, 這時並不會去確認參照的 secret 是否存在。 一旦 Pod 被分配了, kubelet 將會試圖取得 secret 值, 如果 secret 無法被取得, 因為它並不存在或者是暫時性的與 API server 失去連線, 那 kubelet 將會週期性的重試。 同時 kubelet 也會回報事件, 解釋 Pod 尚未開始的原因。 一旦 secret 被取得, kubelet 將會建立並掛載含有 secret 的 volume。 在 Pod 的 volume 成功掛載之前, 該 Pod 下的任何一個容器都不會開始。



使用情境

使用情境:Pod 及 ssh keys

  • 建立包含一些 ssh key 的 secret:

    kubectl create secret generic ssh-key-secret \
    --from-file=ssh-privatekey=/path/to/.ssh/id_rsa \
    --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
  • 輸出類似:

    secret "ssh-key-secret" created
  • 你也可以建立一個 kustomization.yaml 檔案, 在其 secretGenerator 欄位定義 ssh keys

注意: 在送出你的 ssh keys 之前要三思而後行: 叢集中的其他使用者或許可以存取這個 secret。 使用一個你想要可以被叢集中的其他使用者存取的 service account, 這樣當有什麼狀況時, 你也方便取消這個 service account

  • 現在你可以建立一個 Pod, 這個 Pod 參照一個有 ssh key 的 secret 且以 volume 的方式使用這個 secret

    apiVersion: v1
    kind: Pod
    metadata:
    name: secret-test-pod
    labels:
    name: secret-test
    spec:
    volumes:
    - name: secret-volume
    secret:
    secretName: ssh-key-secret
    containers:
    - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
    readOnly: true
    mountPath: "/etc/secret-volume"
  • 當容器的指令執行後, key 將會在以下的地方可用:

    /etc/secret-volume/ssh-publickey
    /etc/secret-volume/ssh-privatekey

然後該容器將可使用 secret 資料來建立一個 ssh 連線


使用情境: 含有 prod / test 資訊的 Pods

這個範例演示了一個使用含有 production 敏感資訊的 secret 的 Pod, 以及另外一個使用含有 test 敏感資訊的 secret 的 Pod

  • 你可以使用 kustomization.yaml 檔案中的 secretGenerator 欄位來執行 kubectl create secret 指令

    kubectl create secret generic prod-db-secret \
    --from-literal=username=produser \
    --from-literal=password=Y4nys7f11
  • 輸出類似:

    secret "prod-db-secret" created
  • 執行:

    kubectl create secret generic test-db-secret \
    --from-literal=username=testuser \
    --from-literal=password=iluvtests
  • 輸出類似:

    secret "test-db-secret" created

注意:
特殊符號像是 $, \, *, 以及 ! 會被你的 shell 轉換, 所以會需要 escape。 在大部分的 shell 當中, escape password 最簡單的方法就是用 single quote (') 把 string 包住。 舉例來說, 如果你的密碼是 S!B\*d$zDsb, 你應執行指令如下:

kubectl create secret generic dev-db-secret \
--from-literal=username=devuser \
--from-literal=password='S!B\*d$zDsb'

如果使用 --from-file 的話, 你不需要 escape 特殊符號

  • 建立該 pod.yaml

    cat <<EOF > pod.yaml
    apiVersion: v1
    kind: List
    items:
    - kind: Pod
    apiVersion: v1
    metadata:
    name: prod-db-client-pod
    labels:
    name: prod-db-client
    spec:
    volumes:
    - name: secret-volume
    secret:
    secretName: prod-db-secret
    containers:
    - name: db-client-container
    image: myClientImage
    volumeMounts:
    - name: secret-volume
    readOnly: true
    mountPath: "/etc/secret-volume"
    - kind: Pod
    apiVersion: v1
    metadata:
    name: test-db-client-pod
    labels:
    name: test-db-client
    spec:
    volumes:
    - name: secret-volume
    secret:
    secretName: test-db-secret
    containers:
    - name: db-client-container
    image: myClientImage
    volumeMounts:
    - name: secret-volume
    readOnly: true
    mountPath: "/etc/secret-volume"
    EOF
  • 將 pod.yaml 加到 kustimization.yaml file

    cat <<EOF >> kustomization.yaml
    resources:
    - pod.yaml
    EOF
  • 使用以下指令來 apply 這些物件到 API server

    kubectl apply -k .
  • 兩個容器在各自的檔案系統中都會有以下檔案, 檔案的值為各自容器的環境

    /etc/secret-volume/username
    /etc/secret-volume/password

注意到, 兩個 Pods 的 specs 只有一個欄位不同。 它從一個共同的 Pods template 當中使用不同資料來建立 Pods
你可以更進一步的簡化 Pod specification, 藉由使用兩個 service account

  • prod-user 含有 prod-db-secret
  • test-user 含有 test-db-secret

簡化後的 Pod 規格像是:

apiVersion: v1
kind: Pod
metadata:
name: prod-db-client-pod
labels:
name: prod-db-client
spec:
serviceAccount: prod-db-client
containers:
- name: db-client-container
image: myClientImage

使用情境: secret volume 中的 dotfile

你可以讓你的檔案變成隱藏檔, 藉由定義 key 時, 使用 (.) 開頭定義。
這個 key 代表一個 dotfile, 或隱藏檔。 舉例來說, 當以下的 secret 被掛載到 volume, secret-volume

apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
name: secret-dotfiles-pod
spec:
volumes:
- name: secret-volume
secret:
secretName: dotfile-secret
containers:
- name: dotfile-test-container
image: k8s.gcr.io/busybox
command:
- ls
- "-l"
- "/etc/secret-volume"
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"

這個 volume 將會只含有一個檔案, .secret-file, 然後 dotfile-test-container 將會印出 /etc/secret-volume/.secret-file

注意: 檔案若為 dot 開頭的話, 用 ls -l 是印不出來的, 要使用 ls -la


使用情境: Secret 只被 Pod 中的一個容器看見

試想, 當一個應用需要處理 HTTP requests, 一些複雜的商業邏輯, 然後使用 HMAC 來對一些訊息做簽名。
因為它具有複雜應用邏輯, 可能會讓一些軟體讀取 server 中的檔案, 這很可能會暴露 private key
所以這個應用可以分成兩個容器兩個程序: 一個前端容器, 負責處理使用者互動以及商業邏輯, 但不可看到 private key; 以及另外一個 signer 容器, 它可以看到 private key, 回應來自於前端容器的簡單簽名請求。 (舉例來說, 經由 localhost 網路)
經由這個分離的方法, 攻擊者必須要駭進 server 之後, 還要做更細微的控制, 跟單純的讀取檔案動作相比, 這明顯難多了。



最佳實踐

使用 Secret API 的 Clients

當部署一個與 Secret API 有互動的應用時, 應該要使用 authorization policies, 像是 RBAC, 來限制存取權

Secret 很常會含有重要資訊, 其中有很多都會在 Kubernetes 內到外部系統造成很大的影響。 儘管每個 App 都可聲明各自需要的 Secret, 但其他在同一個命名空間下的 App 依然可以非法的獲得該 Secret

因為以上這些原因, 在一個 namespace 下, watch 以及 list 的請求具有極大的權限, 應該被避免, 因為 listing secret 將允許客戶端檢視所有位於該 namespace 下的 secret 值。 可以 watch 以及 list 叢集下所有 secrets 的能力, 應該要被限制, 唯有最高權限或系統等級的元件可以獲得。

需要存取 Secret API 的應用應該要對其需要的 secrets 使用 get 請求。 這讓管理者可以限制所有 secrets 的存取權, 只開放白名單給應用程式需要的 instance 使用 white-listing access to individual instances

get 更好的方式, 客戶端可以設計參照 secret 的資源, 然後 watch 這個資源, 當參照有變更時, 重新對 secret 發請求。 另外, bulk watch API 讓客戶端可以 watch 每一個被提交的資源, 並且可能會在近期由 Kubernetes 發佈。



Security properties

因為 secretes 可以獨立的被建立, 不受限於使用他們的 Pods, 所以在建立, 檢視, 以及編輯 Pods 的過程中暴露 secrets 的風險算是比較低的。 系統也可以採取額外的防護措施, 例如避免將 secret 寫入 disk

secret 唯有當 Pod 需要時才會被送往該 Pod 所在的 node。 kubelet 會將 secret 儲存在 tmpfs, 所以 secret 不會被寫入 disk 儲存區。 一旦有需要該 secret 的 Pod 被刪除了, kubelet 將會從本地複製區刪除該 secret

在一個 node 當中可能會有許多 Pods 會用到的 secret, 然而, secret 只對那些有用到他們的 pod 來說是可見的, 換句話說, 一個 Pod 是無法存取另外一個 Pod 的 secret

在一個 Pod 內可能會有很多容器, 然而, 每個容器都必須對各自 volumeMounts 中的 secret 發請求, 這樣 secret 對該容器來說才會是可見的。 這可被用來建立有用的 security partitions at Pod level (Pod 層級的安全分區)

在多數的 Kubernetes distributions 當中, 使用者和 API server 之間, 以及 API server 到 kubelets 的通訊都由 SSL/TLS 所保護著。 Secret 經由這些通道傳輸時也被保護著。

FEATURE STATE: kubernetes v1.13

你可以針對 secret 資料開啟 encryption at rest, 所以 secret 不會被明碼存進 etcd

Risks

在 API server 中, secret 資料被存在 etcd; 因此:
  • 管理者須針對叢集資料啟動 encryption at rest (從 v1.13 開始以及之後的版本有要求)
  • 管理者須限制到 etcd 以及 admin users 的存取
  • 當原本由 etcd 使用的 disk 不再被使用後, 須將資料清理掉
  • 當叢集中運行 etcd, 管理者務必確保在 etcd 點對點傳輸時有使用 SSL/TLS
如果你透過 manifest (JSON 或 YAML) 檔案配置 secret, 這個 secret 有著 base64 編碼的資料, 那麼分享這個檔案或是將它置於 source repository 代表 secret 將處於風險中。 Base64 編碼並不算是一種加密的方法, 其實跟沒編碼前一樣都是赤裸裸的文字。
從 volume 讀取到 secret 值後仍須注意其安全性, 例如不可意外的將它記到 log 中或是傳送到不被信任的地方
如果使用者可以建立一個使用 secret 的 Pod, 那麼這個使用者將可以看到這個 secret 的值。 儘管 API server 政策不允許這個使用者讀取 secret, 這個使用者可以透過運行 Pod 來暴露 secret
現階段, 任何具有 root 權限的都可在任何一個 node 經由 kubelet 從 API server 讀取任何 secret。 目前有一個計劃中的功能是只會將 secret 送到真正會用到它的 node, 這樣就可以限制 root 在一個 node 上可以造成的影響


Questions and Answers

Kubernetes 中, 如果一個 secret 在一個 node 當中只被一個 pod 使用, 現在該 pod 被刪除了, kubelet 會如何處置這個 secret?

刪除 local copy

Kubernetes 中, kubelet 不會將 secret 儲存到 disk storage, 反之, 會存在哪?

tmpfs

Kubernetes 中, kubelet 會將 secret 儲存到 disk storage 嗎?

不會

Kubernetes 中, secret 在什麼情況下會被送往一個 node?

當該 node 被分配到一個需要該 secret 的 Pod

Kubernetes 中, 什麼情況之下我可能會需要讓 Pod 中特定的容器才可看到 private key?

提高安全性

Kubernetes 中, 如果我要讓 secret 檔案掛載後的檔案是隱藏檔, 我可以怎麼做?

在定義 secret key 時, 以 (.) 開頭

解釋以下的 Kubernetes example
  • Example:
    apiVersion: v1
    kind: Secret
    metadata:
    name: dotfile-secret
    data:
    .secret-file: dmFsdWUtMg0KDQo=
  • Answer:
    apiVersion: v1
    kind: Secret
    metadata:
    name: dotfile-secret
    data:
    // 指定該檔案為 dotfile, 代表該 data 掛載的檔案會是隱藏檔
    .secret-file: dmFsdWUtMg0KDQo=
以下的 Kubernetes configuration file 中的 Kind:List 是?:
  • Configuration file:
    cat <<EOF > pod.yaml
    apiVersion: v1
    kind: List
    items:
    - kind: Pod
    apiVersion: v1
    metadata:
    name: prod-db-client-pod
    labels:
    name: prod-db-client
    spec:
    volumes:
    - name: secret-volume
    secret:
    secretName: prod-db-secret
    containers:
    - name: db-client-container
    image: myClientImage
    volumeMounts:
    - name: secret-volume
    readOnly: true
    mountPath: "/etc/secret-volume"
    - kind: Pod
    apiVersion: v1
    metadata:
    name: test-db-client-pod
    labels:
    name: test-db-client
    spec:
    volumes:
    - name: secret-volume
    secret:
    secretName: test-db-secret
    containers:
    - name: db-client-container
    image: myClientImage
    volumeMounts:
    - name: secret-volume
    readOnly: true
    mountPath: "/etc/secret-volume"
    EOF
  • Answer:
    可定義多個 Pod
Kubernetes Secret 中, 當我要建立含有敏感資訊, 像是 ssh keys 時, 最好採用什麼樣的 service account?

可以建立一個 service account, 你可以跟叢集中的其他使用者分享, 萬一情況不對你可以取消這個 service account

Kubernetes Secret 中, 在 Pod 的 volume 被掛載之前, 不相干的容器可以先開始嗎?

不行

Kubernetes Secret 中, 哪個元件會去確認 secret 是否存在?

kubelet

Kubernetes Secret 中, 什麼時候會去確認 secret 是否存在?

當 Pod 已被建立, 並且被分配後

Kubernetes Secret 中, 當 Pod 經由呼叫 API server 被建立時, 此時會去確認參照的 secret 是否存在嗎?

不會

Kubernetes 中, 1badkey 以及 2alsobad 是合法的嗎?

不合法的

Kubernetes 中, 一個 secret 的 size 最大是多少?

1 MiB

Kubernetes 中, 如果我要自動的附加 imagePullSecret, 我可以把這個家 imagePullSecret 附加到哪一個元件上?

service account

Kubernetes 中, 如果我要自動的掛載手動建立的 secret 到 Pods 上, 我可以使用哪一個元件?

PodPreset

解釋以下 Kubernetes example code
  • Example:
    apiVersion: v1
    kind: Pod
    metadata:
    name: foo
    namespace: awesomeapps
    spec:
    containers:
    - name: foo
    image: janedoe/awesomeapp:v1
    imagePullSecrets:
    - name: myregistrykey
  • Answer:
    apiVersion: v1
    kind: Pod
    metadata:
    name: foo
    namespace: awesomeapps
    spec:
    containers:
    - name: foo
    image: janedoe/awesomeapp:v1
    # 指定 pull 鏡像時要使用哪一個 Docker server, 包含帳號密碼等等資訊...
    imagePullSecrets:
    - name: myregistrykey
解釋以下的 Kubernetes CLI
  • Example
    kubectl create secret docker-registry <name> \
    --docker-server=DOCKER_REGISTRY_SERVER \
    --docker-username=DOCKER_USER \
    --docker-password=DOCKER_PASSWORD \
    --docker-email=DOCKER_EMAIL
  • Answer:
    # 建立拉取私人鏡像會用到的 secret
    kubectl create secret docker-registry <name> \
    # 指定 Docker registry server
    --docker-server=DOCKER_REGISTRY_SERVER \
    # 指定 Docker username
    --docker-username=DOCKER_USER \
    # 指定 Docker password
    --docker-password=DOCKER_PASSWORD \
    # 指定 Docker email
    --docker-email=DOCKER_EMAIL
如何用指令的方式來建立 docker-registry 類型的 secret?
kubectl create secret docker-registry <name> \
--docker-server=DOCKER_REGISTRY_SERVER \
--docker-username=DOCKER_USER \
--docker-password=DOCKER_PASSWORD \
--docker-email=DOCKER_EMAIL
解釋以下 Kubernetes Secret example
  • Example:
    apiVersion: v1
    kind: Pod
    metadata:
    name: secret-env-pod
    spec:
    containers:
    - name: mycontainer
    image: redis
    env:
    - name: SECRET_USERNAME
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: username
    - name: SECRET_PASSWORD
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: password
    restartPolicy: Never
  • Answer:
    apiVersion: v1
    kind: Pod
    metadata:
    name: secret-env-pod
    spec:
    containers:
    - name: mycontainer
    image: redis
    env:
    # 定義 Env
    - name: SECRET_USERNAME
    # env 的值來源
    valueFrom:
    # 來自 secret
    secretKeyRef:
    # secret name
    name: mysecret
    # secret key
    key: username
    - name: SECRET_PASSWORD
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: password
    restartPolicy: Never
Kubernetes Secret 中, 怎樣的容器是不會收到 Secret 更新的?

使用 subPath volume 掛載的容器

Kubernetes Secret 中, 當我在容器內印出掛載到該容器內的 secret file 時, 其值是 base64 encoded 還是 decoded?

decoded

解釋以下的 Kubernetes 設定檔
  • Example:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    items:
    - key: username
    path: my-group/my-username
    mode: 511
  • Answer:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    // 針對 mysecret 內的特定的 key 定義路徑以及權限
    items:
    // 針對 key username
    - key: username
    // 路徑為 my-group/my-username, 所以實際 mount 的路徑為 /etc/foo/my-group/my-username
    path: my-group/my-username
    // 由該 secret key 所建立的檔案權限為 511 (511 為十進位), 以八進制換算後實際權限為 `0777`
    mode: 511
Kubernetes Secret 中, 如果我使用 JSON 格式來指定權限的話, 需注意什麼?

JSON 格式不支援八進制, 必須以十進位指定

以下的 Kubernetes 範例中, defaultMode 的意思是?
  • Example
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    defaultMode: 256
  • Answer:
    整個 secret 的預設權限為 256, 256 為十進位, 以八進制換算後為 0400
Kubernetes secret 權限設置中, 如果沒特別指定, 預設權限是?

0644

以下的 Kubernetes example 中, 如果 username 不存在 mysecret 當中, 那會發生什麼事?
  • Example
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    items:
    - key: username
    path: my-group/my-username
  • Answer
    該 volume 不會被建立
以下的 Kubernetes example 中, Secret 的 key username 的 value 實際上 mount 的路徑是?
  • Example
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    items:
    - key: username
    path: my-group/my-username
  • Answer:
    /etc/foo/my-group/my-username
解釋以下 Kubernetes example
  • Example:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
    volumes:
    - name: foo
    secret:
    secretName: mysecret
  • Answer:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
    image: redis
    volumeMounts:
    - name: foo
    # 掛載到 etc 資料夾下的 foo
    mountPath: "/etc/foo"
    # 只允許讀
    readOnly: true
    volumes:
    # 該 volume 名稱為 foo
    - name: foo
    # 來源為 secret
    secret:
    # Secret 名稱
    secretName: mysecret
Kubernetes, 如果我想要使用 CLI 來編輯一個 Secret, 可以怎麼做?
kubectl edit secret mySecret
Kubernetes 中, 如何 decode 下面 example 中的密碼?
  • Example:
    apiVersion: v1
    kind: Secret
    metadata:
    creationTimestamp: 2016-01-22T18:41:56Z
    name: mysecret
    namespace: default
    resourceVersion: "164619"
    uid: cfee02d6-c137-11e5-8d73-42010af00002
    type: Opaque
    data:
    username: YWRtaW4=
    password: MWYyZDFlMmU2N2Rm
  • Answer:
    echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
Kubernetes Secret 中, 當我在 apply kustomization.yaml file 時, kustomization.yaml 需位於?

一個資料夾中

Kubernetes Secret 中, 在 kustomization.yaml 檔案中, 哪一個欄位可以定義如何建立一個 Secret?

secretGenerator

Kubernetes Secret 中, 當我要在 yaml 檔中定義如何建立 Secret 時, 這個 yaml 的檔名必須是?

kustomization.yaml

Kubernetes Secret 中, 在 Linux 上, 當使用 base64 加密時, 當使用 base64 -w 0 加密時, 如果 base64 -w 0 不可得, 可使用什麼其他方式?

base64 | tr -d '\n'

Kubernetes Secret 中, 在 Linux 上, 當使用 base64 加密時, 需特別使用 base64 的哪個 flag 來避免 line wrapping?

-w 0, 可使用 base64 --help 查詢

Kubernetes Secret 中, 當我同時使用 data 以及 stringData 欄位, 如下面範例, 哪一個會被使用?
  • Example:
    apiVersion: v1
    kind: Secret
    metadata:
    name: mysecret
    type: Opaque
    data:
    username: YWRtaW4=
    stringData:
    username: administrator
  • Answer:
    stringData
Kubernetes Secret 中, stringData 會在取得 Secret 時輸出嗎?

不會

Kubernetes Secret 中, datastringData 的差異是?

data 只可輸入 base64 加密後 string
stringData 可接受 plaintext, 然後在建立物件時自動轉為 base64

Kubernetes 中, 當我使用 kubectl getkubectl describe 時, 預設上會不顯示 secret 內容, 主要是要避免哪兩點?
  • 防止旁觀者看到
  • 防止 terminal 的 log 記住
Kubernetes 中, 當我使用 kubectl create secret generic secretName --from-file 來建立 Secret 時, 如果說我的密碼含有特殊符號, 像是 S!B\*d$zDsb, 特殊符號需要 escaping 嗎?

不需要

Kubernetes 中, 當我使用 kubectl create secret generic secretName --from-literal 來建立 Secret 時, 如果說我的密碼含有特殊符號, 像是 S!B\*d$zDsb, 特殊符號需要 escaping 嗎?

需要

Kubernetes 中, 當我使用 kubectl create secret generic scretName --from-literal 來建立 Secret 時, 如果說我的密碼含有特殊符號, 像是 S!B\*d$zDsb, 那我需注意什麼?

用 single quote 將密碼包住

Kubernetes 中, Secret 有哪三種類型?
  • Opaque
  • dockerconfigjson
  • service-account-token
Kubernetes 中, Opaque 類型的 secret 是什麼編碼格式?

base64

Kubernetes 中, dockerconfigjson 類型的 secret 的用途是?

用來儲存私人 docker registry 的認證資訊, 可使用在需要從私人雲端拉取鏡像的 Pod 或 Deployment

Kubernetes 中, secret 的 value 必須先做過什麼處理?

base 64

以下的 Kubernetes 設定檔, 代表什麼意思?
  • 設定檔:
    apiVersion: v1
    kind: Secret
    metadata:
    name: mysecret
    type: Opaque
    data:
    username: YWRtaW4=
    password: YWRtaW4zMjE=
  • Answer:
    # API version
    apiVersion: v1
    # 種類為 Secret
    kind: Secret
    # 此 secret 的 metadata
    metadata:
    # 此 secret 的 name
    name: mysecret
    # 此 secret 的類型為 Opaque
    type: Opaque
    # 此 secret 內容
    data:
    # key 為 username, value 為 base64 編碼後的 string
    username: YWRtaW4=
    # key 為 password, value 為 base64 編碼後的 string
    password: YWRtaW4zMjE=
以下的 Kubernetes 設定檔是什麼意思?
  • 設定檔:
    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
  • Answer:
    # API 版本
    apiVersion: v1
    # 種類為 Pod
    kind: Pod
    # Pod 的 metadata
    metadata:
    # Pod 的 name
    name: secret1-pod
    # 運行此 Pod 的規格
    spec:
    # 容器規格
    containers:
    # 容器名稱
    - name: secret1
    # 指定鏡像
    image: busybox
    # 容器啟動後運行的指令
    command: [ "/bin/sh", "-c", "env" ]
    # env, 以下開始定義
    env:
    # env 名稱
    - name: USERNAME
    # env value 的來源
    valueFrom:
    # 使用 secret
    secretKeyRef:
    # secret 的 name
    name: mysecret
    # secret 的 key
    key: username
    # env 名稱
    - name: PASSWORD
    # env value 的來源
    valueFrom:
    # 使用 secret
    secretKeyRef:
    # secret 的 name
    name: mysecret
    # secret 的 key
    key: password
Kubernetes 的 secret 通常有哪兩種使用方式?
  • 環境變量
  • 掛載 volume
以下的 Kubernetes 設定檔是什麼意思?
  • 設定檔:
    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
  • Answer:
    # API 版本
    apiVersion: v1
    # 種類為 Pod
    kind: Pod
    # Pod 的 metadata
    metadata:
    # Pod 的 name
    name: secret2-pod
    # 此 Pod 運行的規格
    spec:
    # 容器規格
    containers:
    # 容器名稱
    - name: secret2
    # 指定鏡像
    image: busybox
    # 容器啟動後運行的指令
    command: ["/bin/sh", "-c", "ls /etc/secrets"]
    # 使用 volume, 定義如下:
    volumeMounts:
    # 使用名為 secrets 的 volume
    - name: secrets
    # 掛載路徑
    mountPath: /etc/secrets
    # 定義 volume 資訊
    volumes:
    # volume 的 name
    - name: secrets
    # 使用 secret 為 volume 的 value
    secret:
    # secret 的 name
    secretName: mysecret
Kubernetes 中, 如何建立一個 dockerconfigjson 的 secret?
kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
Kubernetes 中, 在建立一個 dockerconfigjson 的 secret 之後, 如何取得 output?
kubectl get secret secretName -o=yaml/json


Laravel - The Basics - Views (官方文件原子化翻譯筆記) Laravel - The Basics - Requests (官方文件原子化翻譯)

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×