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
檔案
# 建立需要的檔案, 之後的範例會用到 |
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
以及 stringData
。 data
欄位用來儲存任意的資料, 使用 base64 加密。 stringData
讓你可以提供未加密的 string
舉例來說, 使用 data
欄位來儲存兩個 string, 將 string 如下轉為 base64
- 執行指令
echo -n 'admin' | base64
- 輸出類似
YWRtaW4=
- 執行指令
echo -n '1f2d1e2e67df' | base64
- 輸出類似
MWYyZDFlMmU2N2Rm
建立一個 Secret YAML file 如下:
apiVersion: v1 |
然後使用 kubectl apply 來建立
- 執行指令
kubectl apply -f ./secret.yaml
- 輸出類似
secret "mysecret" created
某些情況之下, 你可能會需要使用 stringData
欄位。 這個欄位讓你可以輸入一個未經 base64 加密的 string, 然後這個 string 會在 Secret 被建立或更新時自動加密
一個例子是, 當你在部署一個應用, 這個應用使用 Secret 來儲存設定檔, 然後你想要在部署過程中載入設定檔中的部份資訊
舉例來說, 你的應用使用以下設定檔
apiUrl: "https://my.api.com/api/v1" |
你可以使用以下的定義來將這個設定檔儲存到 Secret
apiVersion: v1 |
你的部署工具可以在執行 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 |
結果輸出如下:
apiVersion: v1 |
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 |
Apply 含有 kustomization.yaml
檔資料夾來建立 Secret:
kubectl apply -k . |
輸出類似:
secret/db-user-pass-96mffmfh4k created |
經由以下指令確認 secret 已建立
kubectl get secrets |
輸出類似:
NAME TYPE DATA AGE |
輸入指令
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 |
每個你想要使用的 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 |
然後, 這個 secret 將會被掛載到 /etc/foo
, 且所有由此 secret volume 建立的檔案權限皆為 0400
注意到, 因為 JSON 格式並不支援八進制符號, 所以使用 256 來代表 0400 權限。 如果你使用 YAML 而不是 JSON 的話, 你可以直接使用八進制來指定權限, 這樣看起來更自然。
如之前的範例, 你也可以使用 mapping 來針對不同的檔案指定不同的權限, 如下:
apiVersion: v1 |
在這個範例中, 檔案 /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 struct 的 ConfigMapAndSecretChangeDetectionStrategy
欄位所定義。 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 |
從環境變數來使用 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-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 |
使用情境: secret volume 中的 dotfile
你可以讓你的檔案變成隱藏檔, 藉由定義 key 時, 使用 (.
) 開頭定義。
這個 key 代表一個 dotfile, 或隱藏檔。 舉例來說, 當以下的 secret 被掛載到 volume, secret-volume
apiVersion: v1 |
這個 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 附加到哪一個元件上?
Kubernetes 中, 如果我要自動的掛載手動建立的 secret 到 Pods 上, 我可以使用哪一個元件?
解釋以下 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> \ |
解釋以下 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 中, data
與 stringData
的差異是?
data
只可輸入 base64 加密後 stringstringData
可接受 plaintext, 然後在建立物件時自動轉為 base64
Kubernetes 中, 當我使用 kubectl get
或 kubectl 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 |
留言