用 Google Secret Manager 與 External Secret Operator 管理機敏資料
用 Google Secret Manager 與 External Secret Operator 管理機敏資料
TL;DR
- Google Secret Manager(GSM) 集中管理機敏資料
- Workload Identity(WI) 的方式賦予 External Secret Operator(ESO) 訪問 GSM 的權限
- ESO 同步 GKE & GSM 的 secret
背景
有些服務會使用到環境變數 .env 以 kind: secret 的方式部署,在管理上不便
GSM 機敏資料建立
在 Secret value 的部分,是以 json 的格式存放 key-value 
WI 授予權限
WI 是透過 Google ServiceAccount(GSA) 與 Kubernetes ServiceAccount(KSA) 的綁定,讓 KSA 擁有 GSA 的權限同時,間接授予擁有該 KSA 的服務享有相同權限
👉 先賦予 GSA 權限同時,綁定 KSA 這邊是透過 IaC(terragrunt) 的方式,建立 GSA 賦予權限,並且綁定 KSA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# main.tf
resource "google_service_account" "sa" {
project = var.project_id
account_id = var.name
display_name = var.display_name
description = var.description
}
resource "google_project_iam_member" "roles" {
for_each = toset(var.project_roles)
project = var.project_id
role = each.value
member = google_service_account.sa.member
}
# Workload Identity 綁定:允許 K8s SA impersonate GSA
resource "google_service_account_iam_member" "workload_identity" {
service_account_id = google_service_account.sa.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[${var.k8s_namespace}/${var.k8s_sa_name}]"
}
1
2
3
4
5
6
7
8
# terragrunt.hcl
project_roles = [
"roles/secretmanager.secretAccessor" # 視實際需求調整
]
k8s_namespace = "external-secrets" # 視實際部署調整
k8s_sa_name = "external-secret-external-secrets" # 視實際部署調整
ESO 部署
這邊要指明 KSA 所對應的 GSA 權限來源
可以參考 ArtifactHub 與 Github 找到怎麼設定
1
2
3
4
serviceAccount:
create: true
annotations:
iam.gke.io/gcp-service-account: "${GSA-name}@${GCP-project-id}.iam.gserviceaccount.com"
👉 接著,我們會需要一個 ClusterSecretStore 來讓 ESO 知道 secret 來源
會需要設置
- project: GCP project-id
- clusterLocation: GKE location
- clusterName: GKE cluster name
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# ClusterSecretStore.yaml apiVersion: external-secrets.io/v1 kind: ClusterSecretStore metadata: name: labels: project: spec: provider: gcpsm: projectID: auth: workloadIdentity: clusterLocation: clusterName: serviceAccountRef: name: external-secret-external-secrets namespace: external-secrets
到這邊,應該可以看到上面這個 ClusterSecretStore 的狀態要是 True
1
2
3
4
5
6
# 範例
kubectl get clustersecretstores.external-secrets.io
NAME AGE STATUS CAPABILITIES READY
gcp-secret-manager 117m Valid ReadWrite True
服務取得 GSM secret
服務端則是建立好 ExternalSecret 後,剩下的 dirty work ESO 會幫我們處理好
在 ExternalSecret 中會有幾項需要 define:
- refreshInterval: 更新時間
- secretStoreRef: 參考的類型(文章使用 ClusterSecretStore),以及建立時的名字
- target: 建立的 secret 名稱
- dataFrom: 資料來源(GSM 的 secret name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name:
namespace:
spec:
# 這將「每1分鐘」刷新一次金鑰。可以根據自己的要求保留時間。
refreshInterval:
secretStoreRef:
name:
kind:
target:
name:
creationPolicy:
dataFrom:
- extract:
key:
後續 ESO 會透過上述設定,到 GSM 取值,並依照 ExternalSecret 的設定,到指定 ns 建立 secret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 範例
kubectl -n cosparks describe secret kk
Name: kk
Namespace: ${namespace}
Labels: app.kubernetes.io/managed-by=Helm
reconcile.external-secrets.io/managed=true
Annotations: meta.helm.sh/release-name: ${server}
meta.helm.sh/release-namespace: ${namespace}
Type: Opaque
Data
====
kk: 12 bytes
ping: 4 bytes
1
2
3
4
5
6
const dotenv = require('dotenv');
dotenv.config();
app.get('/', (req, res) => {
res.send(`Hello CoSparks, ${process.env.kk}`);
});
同時 GSM 若更新, ESO 會依照 refreshInterval 更新 K8s 內的 secret 後續服務只需要重啟,就可以吃到新後的 secret
This post is licensed under CC BY 4.0 by the author.
