Kubernetes External Secrets Operator

Kubernetes External Secrets Operator for Secret Management

前言

在現代雲原生架構中,機密資料(Secrets)的管理是一個關鍵的安全議題。雖然 Kubernetes 原生提供了 Secret 資源來儲存敏感資訊,但這些 Secret 僅以 Base64 編碼儲存,並非真正的加密。此外,在多叢集環境中同步和管理 Secret 也是一大挑戰。

External Secrets Operator (ESO) 是一個 Kubernetes Operator,它可以將外部機密管理系統(如 AWS Secrets Manager、HashiCorp Vault、Azure Key Vault 等)中的機密資料同步到 Kubernetes Secret。這讓我們能夠利用專業的機密管理平台來集中管理敏感資訊,同時保持 Kubernetes 應用程式的相容性。

External Secrets Operator 概述

核心概念

External Secrets Operator 提供了以下核心自訂資源(Custom Resources):

資源類型說明
SecretStore定義如何連接到外部機密管理系統(Namespace 範圍)
ClusterSecretStore定義如何連接到外部機密管理系統(叢集範圍)
ExternalSecret定義要從外部系統同步哪些機密資料
ClusterExternalSecret跨多個 Namespace 建立 ExternalSecret
PushSecret將 Kubernetes Secret 推送到外部系統(反向同步)

工作流程

1
2
3
4
5
┌─────────────────────┐     ┌─────────────────────┐     ┌─────────────────────┐
│  External Secret    │     │  External Secrets   │     │  Kubernetes Secret  │
│  Provider           │ ──▶ │  Operator           │ ──▶ │                     │
│  (Vault/AWS/Azure)  │     │                     │     │                     │
└─────────────────────┘     └─────────────────────┘     └─────────────────────┘
  1. 管理員在 SecretStore 中設定外部機密系統的連線資訊
  2. 開發者建立 ExternalSecret 定義需要同步的機密
  3. ESO 定期從外部系統取得機密資料
  4. ESO 自動建立或更新對應的 Kubernetes Secret

支援的後端系統

External Secrets Operator 支援多種機密管理後端:

  • AWS Secrets Manager
  • AWS Systems Manager Parameter Store
  • HashiCorp Vault
  • Azure Key Vault
  • Google Cloud Secret Manager
  • IBM Cloud Secrets Manager
  • Kubernetes Secret(用於叢集間同步)
  • CyberArk Conjur
  • 1Password
  • Doppler
  • Keeper Security

安裝與設定

使用 Helm 安裝

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 新增 External Secrets Operator Helm 倉庫
helm repo add external-secrets https://charts.external-secrets.io
helm repo update

# 建立專用的 Namespace
kubectl create namespace external-secrets

# 安裝 External Secrets Operator
helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --set installCRDs=true \
  --set webhook.port=9443

# 驗證安裝
kubectl get pods -n external-secrets

進階安裝選項

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 使用自訂配置安裝
helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --set installCRDs=true \
  --set replicaCount=2 \
  --set leaderElect=true \
  --set serviceMonitor.enabled=true \
  --set webhook.certManager.enabled=true \
  --set resources.requests.cpu=100m \
  --set resources.requests.memory=128Mi \
  --set resources.limits.cpu=500m \
  --set resources.limits.memory=512Mi

驗證安裝狀態

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 確認所有 CRD 已安裝
kubectl get crd | grep external-secrets

# 預期輸出:
# clusterexternalsecrets.external-secrets.io
# clustersecretstores.external-secrets.io
# externalsecrets.external-secrets.io
# secretstores.external-secrets.io
# pushsecrets.external-secrets.io

# 確認 Operator Pod 正常運行
kubectl get pods -n external-secrets

# 查看 Operator 日誌
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets

SecretStore 與 ClusterSecretStore

SecretStore(Namespace 範圍)

SecretStore 定義了如何連接到外部機密管理系統,它僅在特定 Namespace 內有效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
          serviceAccountRef:
            name: "vault-auth"

ClusterSecretStore(叢集範圍)

ClusterSecretStore 可被所有 Namespace 中的 ExternalSecret 引用,適合用於共用的機密來源。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: global-vault-store
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      namespace: "admin"  # Vault Enterprise namespace
      auth:
        tokenSecretRef:
          name: "vault-token"
          namespace: "external-secrets"
          key: "token"

驗證 SecretStore 狀態

1
2
3
4
5
6
7
8
# 查看 SecretStore 狀態
kubectl get secretstore -n production

# 查看 ClusterSecretStore 狀態
kubectl get clustersecretstore

# 詳細狀態資訊
kubectl describe secretstore vault-backend -n production

正常運作的 SecretStore 應顯示 Valid 狀態:

1
2
3
4
5
6
7
Status:
  Conditions:
    Last Transition Time:  2025-04-29T10:00:00Z
    Message:               store validated
    Reason:                Valid
    Status:                True
    Type:                  Ready

AWS Secrets Manager 整合

IAM 權限設定

首先建立具有適當權限的 IAM Policy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret",
        "secretsmanager:ListSecrets"
      ],
      "Resource": [
        "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:production/*"
      ]
    }
  ]
}

使用 IRSA(IAM Roles for Service Accounts)

這是在 EKS 上推薦的認證方式:

1
2
3
4
5
6
7
# 建立 IRSA 關聯的 Service Account
eksctl create iamserviceaccount \
  --cluster=my-cluster \
  --namespace=external-secrets \
  --name=external-secrets-sa \
  --attach-policy-arn=arn:aws:iam::123456789012:policy/ExternalSecretsPolicy \
  --approve

設定 ClusterSecretStore

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-1
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

使用 Access Key 認證(替代方案)

如果無法使用 IRSA,可以使用 Access Key:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 建立包含 AWS 認證的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: aws-credentials
  namespace: external-secrets
type: Opaque
stringData:
  access-key-id: "AKIAIOSFODNN7EXAMPLE"
  secret-access-key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            namespace: external-secrets
            key: access-key-id
          secretAccessKeySecretRef:
            name: aws-credentials
            namespace: external-secrets
            key: secret-access-key

ExternalSecret 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-secret
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        # 使用 Go template 格式化
        connection-string: "postgresql://{{ .username }}:{{ .password }}@db.example.com:5432/mydb"
  data:
    - secretKey: username
      remoteRef:
        key: production/database
        property: username
    - secretKey: password
      remoteRef:
        key: production/database
        property: password

HashiCorp Vault 整合

Vault 設定準備

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 啟用 Kubernetes 認證方法
vault auth enable kubernetes

# 設定 Kubernetes 認證
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc:443" \
  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# 建立 Policy
vault policy write external-secrets - <<EOF
path "secret/data/*" {
  capabilities = ["read", "list"]
}
path "secret/metadata/*" {
  capabilities = ["read", "list"]
}
EOF

# 建立 Role
vault write auth/kubernetes/role/external-secrets \
  bound_service_account_names=external-secrets-vault \
  bound_service_account_namespaces=external-secrets \
  policies=external-secrets \
  ttl=24h

Kubernetes ServiceAccount

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets-vault
  namespace: external-secrets

ClusterSecretStore 設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      # 可選:跳過 TLS 驗證(僅用於測試)
      # caProvider:
      #   type: "Secret"
      #   name: "vault-ca"
      #   namespace: "external-secrets"
      #   key: "ca.crt"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
          serviceAccountRef:
            name: "external-secrets-vault"
            namespace: "external-secrets"

使用 AppRole 認證

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-approle
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        appRole:
          path: "approle"
          roleId: "db02de05-fa39-4855-059b-67221c5c2f63"
          secretRef:
            name: "vault-approle-secret"
            namespace: "external-secrets"
            key: "secret-id"

ExternalSecret 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: vault-secret
  namespace: production
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: app-secrets
    creationPolicy: Owner
  data:
    # 取得單一屬性
    - secretKey: api-key
      remoteRef:
        key: apps/myapp/config
        property: api_key
    # 取得整個 Secret
    - secretKey: config
      remoteRef:
        key: apps/myapp/config
  # 或使用 dataFrom 取得所有欄位
  dataFrom:
    - extract:
        key: apps/myapp/credentials

Azure Key Vault 整合

Azure 設定準備

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 建立 Service Principal
az ad sp create-for-rbac --name external-secrets-sp --skip-assignment

# 授予 Key Vault 存取權限
az keyvault set-policy \
  --name my-keyvault \
  --spn <app-id> \
  --secret-permissions get list

# 或使用 Azure RBAC
az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee <app-id> \
  --scope /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>

使用 Service Principal 認證

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 建立包含 Azure 認證的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: azure-sp-credentials
  namespace: external-secrets
type: Opaque
stringData:
  client-id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  client-secret: "your-client-secret"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: azure-keyvault
spec:
  provider:
    azurekv:
      tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      vaultUrl: "https://my-keyvault.vault.azure.net"
      authSecretRef:
        clientId:
          name: azure-sp-credentials
          namespace: external-secrets
          key: client-id
        clientSecret:
          name: azure-sp-credentials
          namespace: external-secrets
          key: client-secret

使用 Workload Identity(AKS 推薦方式)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: azure-keyvault-wi
spec:
  provider:
    azurekv:
      tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      vaultUrl: "https://my-keyvault.vault.azure.net"
      authType: WorkloadIdentity
      serviceAccountRef:
        name: external-secrets-sa
        namespace: external-secrets

ExternalSecret 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: azure-secret
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: azure-keyvault
    kind: ClusterSecretStore
  target:
    name: app-credentials
    creationPolicy: Owner
  data:
    - secretKey: database-password
      remoteRef:
        key: database-password
    - secretKey: api-key
      remoteRef:
        key: api-key
    # 取得特定版本
    - secretKey: old-password
      remoteRef:
        key: database-password
        version: "abc123def456"

ExternalSecret 資源定義

基本結構

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-external-secret
  namespace: default
spec:
  # 同步間隔
  refreshInterval: 1h

  # 參照的 SecretStore
  secretStoreRef:
    name: my-secret-store
    kind: SecretStore  # 或 ClusterSecretStore

  # 目標 Kubernetes Secret 設定
  target:
    name: my-secret
    creationPolicy: Owner
    deletionPolicy: Retain
    template:
      type: kubernetes.io/tls
      metadata:
        labels:
          app: myapp
        annotations:
          description: "Synced from external store"
      data:
        tls.crt: "{{ .certificate }}"
        tls.key: "{{ .private_key }}"

  # 資料映射
  data:
    - secretKey: certificate
      remoteRef:
        key: path/to/cert
        property: cert
    - secretKey: private_key
      remoteRef:
        key: path/to/cert
        property: key

  # 或使用 dataFrom 批量取得
  dataFrom:
    - extract:
        key: path/to/secrets
    - find:
        name:
          regexp: "^app-.*"

creationPolicy 選項

Policy說明
OwnerESO 建立並擁有 Secret,ExternalSecret 刪除時 Secret 也會被刪除
OrphanESO 建立 Secret,但 ExternalSecret 刪除時 Secret 會保留
Merge合併到現有 Secret,不會覆蓋其他欄位
None不建立 Secret,僅更新現有 Secret

deletionPolicy 選項

Policy說明
DeleteExternalSecret 刪除時,刪除目標 Secret
MergeExternalSecret 刪除時,僅移除由 ESO 管理的欄位
RetainExternalSecret 刪除時,保留目標 Secret

使用 Template 轉換資料

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: templated-secret
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: database-config
    template:
      engineVersion: v2
      data:
        # 使用 Go template 語法
        config.yaml: |
          database:
            host: {{ .db_host }}
            port: {{ .db_port | default "5432" }}
            username: {{ .db_user }}
            password: {{ .db_pass }}
            ssl_mode: {{ .ssl_mode | default "require" }}          
        # 條件判斷
        connection-string: |
          {{- if eq .db_type "postgres" -}}
          postgresql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:{{ .db_port }}/{{ .db_name }}
          {{- else if eq .db_type "mysql" -}}
          mysql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:{{ .db_port }}/{{ .db_name }}
          {{- end -}}          
  data:
    - secretKey: db_host
      remoteRef:
        key: database/config
        property: host
    - secretKey: db_port
      remoteRef:
        key: database/config
        property: port
    - secretKey: db_user
      remoteRef:
        key: database/credentials
        property: username
    - secretKey: db_pass
      remoteRef:
        key: database/credentials
        property: password
    - secretKey: db_type
      remoteRef:
        key: database/config
        property: type
    - secretKey: db_name
      remoteRef:
        key: database/config
        property: name

使用 dataFrom 批量同步

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: bulk-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: all-app-secrets
  dataFrom:
    # 取得單一路徑下的所有 key
    - extract:
        key: apps/myapp/config

    # 使用正則表達式搜尋多個 Secret
    - find:
        path: apps/myapp
        name:
          regexp: "^(db|cache|api)-.*"

    # 使用標籤過濾
    - find:
        tags:
          environment: production
          team: backend

ClusterExternalSecret(跨 Namespace)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
  name: shared-tls-cert
spec:
  # 選擇目標 Namespace
  namespaceSelector:
    matchLabels:
      needs-tls: "true"
  # 或使用 matchExpressions
  # namespaceSelector:
  #   matchExpressions:
  #     - key: environment
  #       operator: In
  #       values: ["staging", "production"]

  # ExternalSecret 模板
  externalSecretSpec:
    refreshInterval: 24h
    secretStoreRef:
      name: vault-backend
      kind: ClusterSecretStore
    target:
      name: wildcard-tls
      creationPolicy: Owner
      template:
        type: kubernetes.io/tls
    data:
      - secretKey: tls.crt
        remoteRef:
          key: certificates/wildcard
          property: certificate
      - secretKey: tls.key
        remoteRef:
          key: certificates/wildcard
          property: private_key

自動輪換與同步策略

設定 refreshInterval

refreshInterval 決定 ESO 多久從外部系統同步一次機密資料:

1
2
3
4
5
6
7
8
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: high-frequency-sync
spec:
  # 每 5 分鐘同步一次
  refreshInterval: 5m
  # ...

常見的設定建議:

使用情境建議間隔
開發/測試環境1m - 5m
一般生產環境15m - 1h
變動頻率低的機密1h - 24h
憑證(接近到期時)5m - 15m

強制立即同步

1
2
3
4
5
6
7
# 透過重新套用 ExternalSecret 觸發同步
kubectl annotate externalsecret my-secret \
  force-sync=$(date +%s) --overwrite

# 或刪除並重建
kubectl delete externalsecret my-secret
kubectl apply -f externalsecret.yaml

監控同步狀態

1
2
3
4
5
6
7
8
# 查看 ExternalSecret 狀態
kubectl get externalsecret -A

# 詳細狀態資訊
kubectl describe externalsecret my-secret

# 查看事件
kubectl get events --field-selector involvedObject.name=my-secret

正常同步的狀態:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Status:
  Binding:
    Name: my-secret
  Conditions:
    - Last Transition Time: 2025-04-29T10:00:00Z
      Message: Secret was synced
      Reason: SecretSynced
      Status: "True"
      Type: Ready
  Refresh Time: 2025-04-29T10:00:00Z
  Synced Resource Version: 1-abc123

處理同步失敗

設定重試策略:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: resilient-secret
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: app-secret
    creationPolicy: Owner
  # 資料取得失敗時的處理策略
  dataFrom:
    - extract:
        key: apps/config
        conversionStrategy: Default
        decodingStrategy: None

搭配 Reloader 自動重啟應用

當 Secret 更新時,應用程式需要重新載入配置。可以使用 Reloader 自動化這個過程:

1
2
3
# 安裝 Reloader
helm repo add stakater https://stakater.github.io/stakater-charts
helm install reloader stakater/reloader -n kube-system
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  annotations:
    # Reloader 會監控此 Secret 的變更
    reloader.stakater.com/auto: "true"
    # 或指定特定 Secret
    # secret.reloader.stakater.com/reload: "my-secret"
spec:
  template:
    spec:
      containers:
        - name: app
          envFrom:
            - secretRef:
                name: my-secret

進階輪換策略

結合外部系統的機密輪換功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# AWS Secrets Manager 自動輪換設定
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: rotating-db-creds
spec:
  # 配合 AWS 輪換週期
  refreshInterval: 4h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    template:
      data:
        # AWS Secrets Manager 會在輪換期間提供 AWSCURRENT 和 AWSPREVIOUS
        current-password: "{{ .password }}"
  data:
    - secretKey: password
      remoteRef:
        key: production/database
        property: password
        # 使用 version stage 取得目前版本
        version: AWSCURRENT

PushSecret(反向同步)

將 Kubernetes Secret 推送到外部系統:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: external-secrets.io/v1beta1
kind: PushSecret
metadata:
  name: push-to-vault
  namespace: production
spec:
  # 同步間隔
  refreshInterval: 10s

  # 來源 Secret
  selector:
    secret:
      name: generated-cert

  # 目標 SecretStore
  secretStoreRefs:
    - name: vault-backend
      kind: ClusterSecretStore

  # 推送設定
  data:
    - match:
        secretKey: tls.crt
        remoteRef:
          remoteKey: certificates/app-cert
          property: certificate
    - match:
        secretKey: tls.key
        remoteRef:
          remoteKey: certificates/app-cert
          property: private_key

最佳實務

1. 安全性考量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 使用 RBAC 限制 SecretStore 存取
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: external-secrets-user
  namespace: production
rules:
- apiGroups: ["external-secrets.io"]
  resources: ["externalsecrets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["external-secrets.io"]
  resources: ["secretstores"]
  verbs: ["get", "list"]  # 唯讀
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: external-secrets-user-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: developer-sa
  namespace: production
roleRef:
  kind: Role
  name: external-secrets-user
  apiGroup: rbac.authorization.k8s.io

2. 使用 ClusterSecretStore 共享配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 管理員維護的 ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: production-vault
spec:
  # 限制可使用此 Store 的 Namespace
  conditions:
    - namespaces:
        - production
        - staging
  provider:
    vault:
      server: "https://vault.example.com:8200"
      # ...

3. 監控與告警

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: external-secrets
  namespace: external-secrets
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: external-secrets
  endpoints:
    - port: metrics

關鍵指標:

  • externalsecret_sync_calls_total - 同步呼叫次數
  • externalsecret_sync_calls_error - 同步錯誤次數
  • externalsecret_status_condition - 目前狀態

4. 災難復原

1
2
3
4
5
# 匯出所有 ExternalSecret 定義
kubectl get externalsecrets -A -o yaml > externalsecrets-backup.yaml

# 匯出 ClusterSecretStore 定義(不含敏感認證)
kubectl get clustersecretstores -o yaml > clustersecretstores-backup.yaml

5. 版本控制 ExternalSecret

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用 GitOps 管理 ExternalSecret
# externalsecrets/production/database.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
  labels:
    app.kubernetes.io/managed-by: argocd
    environment: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: production-vault
    kind: ClusterSecretStore
  target:
    name: db-credentials
  data:
    - secretKey: password
      remoteRef:
        key: production/database
        property: password

常見問題排解

SecretStore 連線失敗

1
2
3
4
5
6
7
# 檢查 SecretStore 狀態
kubectl describe secretstore my-store

# 常見錯誤:
# - 網路連線問題
# - 認證失敗
# - 權限不足

ExternalSecret 同步失敗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 查看詳細錯誤訊息
kubectl describe externalsecret my-secret

# 查看 Operator 日誌
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets -f

# 常見錯誤:
# - Secret 路徑不存在
# - 欄位名稱錯誤
# - 權限不足

除錯模式

1
2
3
4
# 啟用詳細日誌
helm upgrade external-secrets external-secrets/external-secrets \
  -n external-secrets \
  --set extraArgs[0]="--loglevel=debug"

總結

本文介紹了 Kubernetes External Secrets Operator 的完整使用方式:

  1. 概述:ESO 可將外部機密管理系統的機密同步到 Kubernetes Secret
  2. 安裝:使用 Helm 進行安裝和配置
  3. SecretStore:設定與外部系統的連線方式
  4. 雲端整合:AWS Secrets Manager、HashiCorp Vault、Azure Key Vault 的詳細設定
  5. ExternalSecret:定義機密同步規則和轉換邏輯
  6. 自動輪換:設定同步策略和應用程式重載

External Secrets Operator 提供了一個優雅的方式來整合企業級機密管理系統與 Kubernetes,實現機密的集中管理、自動同步和安全存取。建議根據組織需求選擇適合的機密管理後端,並遵循最佳實務來確保系統安全性。

參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy