在現代雲端原生架構中,Kubernetes 已成為容器編排的標準平台,而 Secrets 管理則是確保系統安全的核心基石。當企業將敏感資訊如資料庫密碼、API 金鑰、憑證檔案等部署至 Kubernetes 叢集時,如何確保這些機密資料的安全性、可用性與可追溯性,成為資訊安全團隊必須面對的嚴峻挑戰。本文將從台灣企業的實際需求出發,探討 Kubernetes Secrets 在生產環境的完整管理策略,涵蓋災難還原架構、加密機制、稽核追蹤,以及與現代 CI/CD 流程的安全整合實務。
Kubernetes Secrets 管理架構與企業級考量
在探討具體實作之前,我們需要先理解 Kubernetes Secrets 的管理架構與企業環境的特殊需求。Kubernetes 提供了內建的 Secrets 物件來儲存敏感資訊,這些資料預設會以 Base64 編碼儲存在 etcd 中。然而,Base64 僅是編碼而非加密,因此在生產環境中必須搭配額外的安全機制。企業在規劃 Secrets 管理策略時,需要同時考量合規要求、災難還原能力、存取控制、稽核追蹤等多個面向,才能建立完整的安全防護體系。
台灣企業面對個人資料保護法、資通安全管理法等法規要求時,必須確保敏感資料的儲存與傳輸都符合加密標準,同時建立完整的存取記錄供稽核使用。此外,金融、醫療等受管制產業還需要符合特定的資安規範,這使得 Secrets 管理不僅是技術問題,更是合規治理的重要環節。因此,企業在選擇 Secrets 管理方案時,需要在安全性、可用性、管理複雜度之間取得適當平衡,同時確保解決方案能夠支援組織的長期發展需求。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
package "應用程式層" {
[微服務應用程式]
[CI/CD Pipeline]
}
package "Kubernetes 叢集" {
[Secret 物件]
[Pod]
[ServiceAccount]
}
package "儲存層" {
[etcd 叢集]
[外部 Secret 儲存]
}
package "安全控制層" {
[加密機制]
[存取控制]
[稽核記錄]
}
[微服務應用程式] --> [Pod] : 部署
[CI/CD Pipeline] --> [Secret 物件] : 建立/更新
[Pod] --> [Secret 物件] : 掛載/讀取
[ServiceAccount] --> [Pod] : 身份驗證
[Secret 物件] --> [etcd 叢集] : 內部儲存
[Secret 物件] --> [外部 Secret 儲存] : 外部整合
[加密機制] --> [etcd 叢集] : 靜態加密
[存取控制] --> [Secret 物件] : RBAC 控制
[稽核記錄] --> [Secret 物件] : 操作記錄
@enduml上述架構圖展示了 Kubernetes Secrets 管理的完整生態系統。應用程式層包含需要存取敏感資訊的微服務與 CI/CD 流程,Kubernetes 叢集層提供 Secret 物件的抽象化介面,儲存層則負責實際的資料持久化,而安全控制層則橫跨整個架構提供多層次的防護機制。這種分層設計使得企業可以根據不同的安全需求選擇適當的實作方式,同時保持架構的彈性與可擴展性。
災難還原策略:確保業務連續性的關鍵設計
災難還原是企業資訊系統規劃中不可或缺的環節,對於儲存敏感資訊的 Kubernetes Secrets 而言更是如此。當主要資料中心發生災難時,能否快速還原 Secrets 資料並恢復服務運作,直接影響企業的業務連續性。Kubernetes Secrets 的災難還原策略主要取決於採用的儲存架構,可以分為使用 etcd 內部儲存與使用外部 Secret 儲存兩大類別,每種方式都有其特定的還原機制與考量重點。
基於 etcd 的災難還原架構
當企業選擇使用 Kubernetes 內建的 etcd 作為 Secrets 儲存後端時,災難還原策略需要聚焦在 etcd 叢集本身的備份與還原機制。etcd 是 Kubernetes 的核心元件,儲存了叢集的所有狀態資訊,包括 Secrets 在內。因此,確保 etcd 的高可用性與災難還原能力,就能同時保障 Secrets 的可用性。企業可以採用多種策略來達成這個目標,從最基本的定期備份到複雜的多地區部署,每種方式都有其適用場景與成本考量。
第一種方式是透過內部工具或雲端服務在不同地區按需建立叢集。這種方法的核心概念是將整個 Kubernetes 叢集的組態與狀態進行備份,當主要叢集發生災難時,可以在另一個地理區域快速建立一個新的叢集並還原所有資料。Velero 是這個領域最流行的開源工具,它不僅支援 Kubernetes 資源的備份與還原,還能處理持久化儲存的快照。對於使用 Google Cloud Platform 的企業,GKE 提供了內建的叢集備份功能,可以自動將叢集組態與 etcd 資料備份到 Cloud Storage,大幅簡化了災難還原的實作複雜度。
# 使用 Velero 執行完整的 Kubernetes 叢集備份
# 此命令會備份指定命名空間中的所有資源,包括 Secrets
velero backup create production-backup \
--include-namespaces production \
--snapshot-volumes \
--ttl 720h
# 當災難發生時,在新叢集中還原備份
# 此命令會還原所有 Kubernetes 資源到新的叢集環境
velero restore create --from-backup production-backup \
--namespace-mappings production:production-restored
# 驗證還原後的 Secrets 是否正確
# 列出所有還原後的 Secrets 物件
kubectl get secrets -n production-restored
# 檢視特定 Secret 的詳細資訊,確認資料完整性
kubectl describe secret database-credentials -n production-restored
第二種方式是維護一個備用 Kubernetes 叢集,透過 CI/CD 管道持續同步 Secrets 到備用環境。這種主動-被動的架構設計讓企業在主叢集發生故障時,可以快速切換到備用叢集繼續提供服務。此方法的關鍵在於確保 Secrets 的同步機制可靠且即時,避免主備叢集之間的資料不一致。透過 GitOps 工具如 ArgoCD 或 Flux,可以實作宣告式的 Secrets 管理,讓同一份組態自動部署到多個叢集,大幅降低管理複雜度。然而,企業需要特別注意 Secrets 在傳輸過程中的安全性,確保敏感資訊不會在同步過程中洩漏。
# ArgoCD Application 組態,實作 Secrets 的多叢集同步
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
# Application 名稱,用於識別此同步任務
name: secrets-sync
# 部署到 ArgoCD 系統命名空間
namespace: argocd
spec:
# 指定 Git 儲存庫作為組態來源
source:
# Git 儲存庫的 URL
repoURL: https://github.com/company/k8s-configs
# 追蹤的分支名稱
targetRevision: main
# Secrets 組態檔案的路徑
path: secrets
# 目標叢集的組態
destination:
# 目標 Kubernetes 叢集的 API 伺服器位址
server: https://backup-cluster.company.com
# 部署的目標命名空間
namespace: production
# 同步策略設定
syncPolicy:
# 啟用自動同步,當 Git 儲存庫變更時自動部署
automated:
# 自動修正叢集中的資源漂移
selfHeal: true
# 自動清理不再需要的資源
prune: true
第三種也是最高階的方式是建立多地區活躍-活躍叢集架構。在這種設計中,企業在多個地理區域同時運作多個 Kubernetes 叢集,每個叢集都能獨立處理請求,並透過全域負載平衡器分配流量。當某個地區發生災難時,流量會自動切換到其他健康的地區,使用者幾乎感受不到服務中斷。然而,這種架構的複雜度相當高,需要處理跨地區的資料同步、一致性保證、網路延遲等問題。對於 Secrets 管理而言,企業需要確保所有地區的 Secrets 保持同步,同時避免因網路分割造成的資料不一致。這種架構通常只適用於對可用性要求極高的關鍵業務系統,因為其實作與維運成本都相當可觀。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
node "台北資料中心" {
[Kubernetes 叢集 A]
[etcd 叢集 A]
[應用程式實例 A]
}
node "台中資料中心" {
[Kubernetes 叢集 B]
[etcd 叢集 B]
[應用程式實例 B]
}
node "高雄資料中心" {
[Kubernetes 叢集 C]
[etcd 叢集 C]
[應用程式實例 C]
}
cloud "全域負載平衡器" {
[流量分配]
}
database "跨地區 Secret 同步" {
[同步機制]
}
[流量分配] --> [應用程式實例 A]
[流量分配] --> [應用程式實例 B]
[流量分配] --> [應用程式實例 C]
[Kubernetes 叢集 A] --> [etcd 叢集 A]
[Kubernetes 叢集 B] --> [etcd 叢集 B]
[Kubernetes 叢集 C] --> [etcd 叢集 C]
[同步機制] --> [etcd 叢集 A]
[同步機制] --> [etcd 叢集 B]
[同步機制] --> [etcd 叢集 C]
@enduml外部 Secret 儲存的災難還原優勢
相對於使用 etcd 內部儲存,採用外部專業的 Secret 管理服務可以大幅簡化災難還原的實作複雜度。這些服務本身就是為了高可用性與災難還原而設計,提供了企業級的可靠性保證。當企業選擇使用外部 Secret 儲存時,災難還原的責任主要由服務提供商承擔,IT 團隊只需要專注在與 Kubernetes 的整合介面上,不需要自行建立複雜的備份與還原機制。
Azure Key Vault 與 Google Cloud Secret Manager 都提供了原生的跨地區可用性功能。這些服務會自動將 Secrets 複製到多個資料中心,確保即使某個地區完全失效,仍然可以從其他地區存取相同的 Secrets。對於台灣企業而言,可以選擇將 Secrets 同時儲存在亞太地區的多個資料中心,例如台灣、香港、新加坡等,既能確保低延遲存取,又能提供災難還原能力。這種設計的優勢在於企業不需要額外維護複雜的同步機制,服務提供商已經處理好所有底層的複製與一致性問題。
# 使用 Google Cloud Secret Manager 建立跨地區 Secret
from google.cloud import secretmanager
import json
def create_replicated_secret(project_id, secret_id, secret_value):
"""
在 Google Cloud Secret Manager 中建立具有跨地區複製功能的 Secret
參數:
project_id: GCP 專案 ID
secret_id: Secret 的唯一識別碼
secret_value: 要儲存的敏感資料
"""
# 建立 Secret Manager 客戶端
client = secretmanager.SecretManagerServiceClient()
# 組建專案路徑
parent = f"projects/{project_id}"
# 設定 Secret 的組態,啟用自動複製到多個地區
secret = {
"replication": {
"automatic": {} # 自動選擇最佳的地理位置進行複製
}
}
# 建立 Secret 物件
response = client.create_secret(
request={
"parent": parent,
"secret_id": secret_id,
"secret": secret
}
)
# 新增 Secret 的版本資料
parent = client.secret_path(project_id, secret_id)
payload = secret_value.encode("UTF-8")
response = client.add_secret_version(
request={
"parent": parent,
"payload": {"data": payload}
}
)
print(f"已建立跨地區複製的 Secret: {response.name}")
return response
# 使用範例:建立資料庫連線字串 Secret
create_replicated_secret(
project_id="my-production-project",
secret_id="database-connection-string",
secret_value="postgresql://user:password@db.company.com:5432/production"
)
AWS Secrets Manager 則提供了更靈活的跨地區複製功能。企業可以明確指定要將 Secrets 複製到哪些地區,並設定主要地區與次要地區的關係。這種精細的控制讓企業可以根據合規要求與效能需求來規劃資料的地理分布。例如,台灣的金融企業可能需要確保資料主要儲存在台灣境內,同時在日本或新加坡維護一份複本以應對災難情境。AWS Secrets Manager 的自動輪換功能還能定期更新敏感資訊如資料庫密碼,進一步提升安全性。
# 使用 AWS Secrets Manager 建立跨地區複製的 Secret
import boto3
import json
def create_secret_with_replication(secret_name, secret_value, replica_regions):
"""
在 AWS Secrets Manager 中建立具有跨地區複製功能的 Secret
參數:
secret_name: Secret 的名稱
secret_value: 要儲存的敏感資料(字典格式)
replica_regions: 要複製到的 AWS 地區列表
"""
# 建立主要地區的 Secrets Manager 客戶端(以 ap-northeast-1 東京為例)
client = boto3.client('secretsmanager', region_name='ap-northeast-1')
# 設定複製組態
replica_config = [
{'Region': region} for region in replica_regions
]
try:
# 建立 Secret 並啟用跨地區複製
response = client.create_secret(
Name=secret_name,
Description='跨地區複製的生產環境 Secret',
SecretString=json.dumps(secret_value),
# 指定要複製到的地區
AddReplicaRegions=replica_config
)
print(f"已建立 Secret: {response['ARN']}")
print(f"複製到地區: {replica_regions}")
return response
except client.exceptions.ResourceExistsException:
# 如果 Secret 已存在,則更新其值
print(f"Secret {secret_name} 已存在,執行更新操作")
response = client.put_secret_value(
SecretId=secret_name,
SecretString=json.dumps(secret_value)
)
return response
# 使用範例:建立資料庫憑證並複製到多個亞太地區
database_credentials = {
"username": "admin",
"password": "SuperSecurePassword123!",
"host": "database.company.com",
"port": 5432,
"database": "production"
}
create_secret_with_replication(
secret_name="production-database-credentials",
secret_value=database_credentials,
# 複製到東京、首爾、新加坡三個地區
replica_regions=['ap-northeast-2', 'ap-southeast-1']
)
HashiCorp Vault Enterprise 版本提供了最靈活的跨地區複製解決方案。Vault 支援效能複製與災難還原複製兩種模式,企業可以根據需求建立複雜的多地區部署架構。效能複製讓企業可以在多個地區建立唯讀副本,減少跨地區存取的延遲,而災難還原複製則提供了完整的故障轉移能力。Vault 還支援命名空間隔離,讓不同的團隊或專案可以在同一個 Vault 叢集中管理各自的 Secrets,同時保持邏輯上的隔離。對於大型企業而言,這種企業級的功能使得 Vault 成為最全面的 Secret 管理解決方案。
# HashiCorp Vault 的災難還原複製組態
# 此組態檔案定義了主要叢集與次要叢集之間的複製關係
# 主要 Vault 叢集組態(位於台北資料中心)
storage "raft" {
# Raft 共識演算法的儲存路徑
path = "/vault/data"
# 定義此節點的唯一識別碼
node_id = "vault-taipei-01"
# 效能複製組態,啟用跨地區的唯讀副本
performance_replication {
# 啟用效能複製功能
enable = true
# 定義此叢集為主要叢集
mode = "primary"
}
# 災難還原複製組態,提供完整的故障轉移能力
dr_replication {
# 啟用災難還原複製
enable = true
# 定義此叢集為主要叢集
mode = "primary"
}
}
# 次要 Vault 叢集組態(位於新加坡資料中心)
# 此組態會部署在災難還原站點
storage "raft" {
path = "/vault/data"
node_id = "vault-singapore-01"
performance_replication {
enable = true
# 定義此叢集為次要叢集,從主叢集同步資料
mode = "secondary"
# 主要叢集的位址
primary_cluster_addr = "https://vault-taipei.company.com:8201"
}
dr_replication {
enable = true
mode = "secondary"
primary_cluster_addr = "https://vault-taipei.company.com:8201"
}
}
# API 位址組態,供外部系統存取
api_addr = "https://vault.company.com:8200"
# 叢集位址組態,供 Vault 節點間通訊使用
cluster_addr = "https://vault.company.com:8201"
加密機制:多層次的安全防護體系
加密是保護 Kubernetes Secrets 最基本也最重要的安全措施。雖然 Kubernetes 的 Secret 物件提供了與 ConfigMap 不同的抽象化,但預設情況下這些資料僅以 Base64 編碼儲存在 etcd 中,並未進行真正的加密。Base64 是一種可逆的編碼方式,任何能夠存取 etcd 的人都可以輕易解碼取得原始的敏感資訊。因此,企業必須實作額外的加密機制來確保 Secrets 的安全性,即使攻擊者取得了 etcd 的備份檔案,也無法讀取其中的敏感資料。
靜態資料加密:保護 etcd 中的 Secrets
Kubernetes 提供了內建的靜態資料加密功能,透過 Encryption Configuration 可以在 API Server 寫入 etcd 之前對 Secrets 進行加密。這種加密是透明的,應用程式在讀取 Secrets 時會自動解密,不需要修改任何程式碼。企業可以選擇多種加密提供者,從簡單的 AES-CBC 到與雲端 Key Management Service 整合的解決方案,每種方式都有其適用場景與安全性考量。
最基本的加密方式是使用 secretbox 提供者,它使用本地金鑰檔案進行加密。這種方法的優點是簡單且不依賴外部服務,但缺點是金鑰管理成為一個挑戰。企業需要確保金鑰檔案本身的安全性,避免與 etcd 資料儲存在同一個位置,否則攻擊者取得伺服器存取權限後仍然可以解密資料。此外,金鑰輪換也需要手動進行,增加了管理複雜度。
# Kubernetes API Server 的加密組態檔案
# 定義如何加密儲存在 etcd 中的 Secret 資料
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
# 指定要加密的資源類型
- resources:
- secrets
providers:
# 使用 AES-CBC 加密演算法,這是最常用的對稱加密方式
- aescbc:
keys:
# 定義加密金鑰,name 用於識別不同的金鑰版本
- name: key1
# 實際的加密金鑰,必須是 32 位元組的 Base64 編碼字串
# 在生產環境中應該使用安全的方式生成與儲存此金鑰
secret: <base64-encoded-32-byte-key>
# identity 提供者表示不加密,用於向後相容
# 當新增加密設定時,現有的未加密資料仍可正常讀取
- identity: {}
更進階的做法是整合雲端服務商提供的 Key Management Service。以 Google Cloud 為例,企業可以使用 Cloud KMS 來管理加密金鑰,Kubernetes API Server 在加密 Secrets 時會呼叫 Cloud KMS 的 API 來取得加密金鑰。這種方式的優勢在於金鑰管理完全由雲端服務商負責,包括金鑰的生成、儲存、輪換、稽核等,企業不需要自行處理這些複雜的安全議題。Cloud KMS 還支援硬體安全模組,提供了更高級別的安全保障。
# 整合 Google Cloud KMS 的 Kubernetes 加密組態
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
# 使用 Google Cloud KMS 作為加密提供者
- kms:
# KMS 提供者的名稱識別
name: gcp-kms-provider
# Cloud KMS 的端點位址
endpoint: unix:///var/run/kmsplugin/socket.sock
# 指定用於加密的 KMS 金鑰資源路徑
# 格式: projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY
cachesize: 1000
# KMS 金鑰的完整資源名稱
keyID: projects/my-project/locations/asia-east1/keyRings/kubernetes/cryptoKeys/secrets-key
# 保留 identity 提供者以支援未加密的舊資料
- identity: {}
Azure 的 AKS 叢集提供了類似的整合,可以使用 Azure Key Vault 來管理加密金鑰。AWS 的 EKS 則可以整合 AWS KMS,提供一致的金鑰管理體驗。這些雲端原生的解決方案不僅簡化了實作複雜度,還提供了與其他雲端服務的無縫整合,例如可以使用相同的 KMS 金鑰來加密資料庫、儲存系統等,實現統一的加密策略。
傳輸加密:保護 Secrets 的網路傳輸
除了靜態資料加密,企業還需要確保 Secrets 在網路傳輸過程中的安全性。Kubernetes 內部元件之間的通訊預設使用 TLS 加密,但企業需要確保這些憑證的安全性與有效性。此外,當應用程式從 API Server 讀取 Secrets 時,也應該使用 TLS 連線,避免敏感資訊在網路上以明文傳輸。
對於多叢集或混合雲環境,企業還需要考慮跨資料中心的 Secrets 同步安全性。使用 Service Mesh 如 Istio 可以為所有服務間的通訊提供自動的 mTLS 加密,確保即使在不受信任的網路環境中,Secrets 也不會洩漏。這種零信任網路架構已成為現代雲端原生應用的標準設計,台灣企業在規劃資安架構時應該將其納入考量。
# Istio Service Mesh 的 PeerAuthentication 組態
# 強制所有服務間通訊使用 mTLS 加密
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
# 組態名稱
name: default
# 部署到 istio-system 命名空間,影響整個網格
namespace: istio-system
spec:
# 設定 mTLS 模式為嚴格模式
# STRICT: 只接受 mTLS 加密的連線,拒絕明文連線
# PERMISSIVE: 同時接受 mTLS 和明文連線,用於遷移期間
# DISABLE: 停用 mTLS,不建議在生產環境使用
mtls:
mode: STRICT
稽核機制:建立完整的操作追蹤體系
稽核是資安管理的重要環節,也是許多合規框架的基本要求。對於 Kubernetes Secrets 而言,完整的稽核記錄可以幫助企業追蹤誰在何時存取或修改了哪些敏感資訊,這對於事後的安全分析與責任歸屬至關重要。台灣企業面對個資法與資通安全管理法時,必須能夠提供完整的存取記錄,證明敏感資料的處理符合法規要求。
Kubernetes 原生稽核功能
Kubernetes 提供了內建的稽核日誌功能,可以記錄對 API Server 的所有請求,包括對 Secrets 的建立、讀取、更新、刪除操作。企業可以透過稽核策略來定義要記錄哪些事件、記錄的詳細程度以及日誌的輸出格式。合理的稽核策略需要在安全性與效能之間取得平衡,記錄過多的細節會影響系統效能並產生大量日誌,但記錄不足又可能錯過重要的安全事件。
# Kubernetes 稽核策略組態
# 定義要記錄哪些 API 請求以及記錄的詳細程度
apiVersion: audit.k8s.io/v1
kind: Policy
# 省略不重要的操作,避免產生過多日誌
omitStages:
- "RequestReceived"
rules:
# 規則 1: 詳細記錄所有 Secrets 操作
- level: RequestResponse
# 適用的資源類型
resources:
- group: ""
resources: ["secrets"]
# 記錄所有命名空間的 Secrets 操作
namespaces: ["*"]
# 記錄的操作類型
verbs: ["get", "list", "create", "update", "patch", "delete"]
# 規則 2: 記錄 ServiceAccount Token 的使用
- level: Metadata
resources:
- group: ""
resources: ["serviceaccounts/token"]
verbs: ["create"]
# 規則 3: 記錄 RBAC 變更,因為權限變更可能影響 Secrets 存取
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# 規則 4: 對其他資源只記錄元資料,減少日誌量
- level: Metadata
omitStages:
- "RequestReceived"
稽核日誌應該輸出到集中式的日誌管理系統,而不是僅儲存在本地檔案中。企業可以使用 Elasticsearch、Splunk、Google Cloud Logging 等服務來收集、分析與儲存稽核日誌。這些系統不僅提供強大的搜尋與視覺化功能,還能設定告警規則,當偵測到異常的 Secrets 存取模式時立即通知資安團隊。例如,如果某個服務帳號突然開始存取大量不同的 Secrets,這可能表示該帳號已被入侵,需要立即調查。
# 使用 Python 分析 Kubernetes 稽核日誌
# 偵測異常的 Secrets 存取模式
import json
from collections import defaultdict
from datetime import datetime, timedelta
def analyze_secret_access_patterns(audit_logs):
"""
分析稽核日誌,找出異常的 Secrets 存取模式
參數:
audit_logs: Kubernetes 稽核日誌的列表
回傳:
可疑活動的列表
"""
# 記錄每個使用者存取的 Secrets
user_access = defaultdict(set)
# 記錄每個使用者的存取時間
user_timestamps = defaultdict(list)
for log in audit_logs:
# 解析 JSON 格式的稽核日誌
event = json.loads(log)
# 只關注 Secrets 相關的操作
if event.get('objectRef', {}).get('resource') != 'secrets':
continue
# 提取使用者資訊
user = event.get('user', {}).get('username', 'unknown')
# 提取存取的 Secret 名稱
secret_name = event.get('objectRef', {}).get('name')
# 提取操作時間
timestamp = event.get('requestReceivedTimestamp')
if secret_name:
user_access[user].add(secret_name)
user_timestamps[user].append(datetime.fromisoformat(timestamp.replace('Z', '+00:00')))
# 偵測異常模式
suspicious_activities = []
for user, secrets in user_access.items():
# 模式 1: 單一使用者在短時間內存取大量不同的 Secrets
if len(secrets) > 10:
timestamps = sorted(user_timestamps[user])
time_span = timestamps[-1] - timestamps[0]
# 如果在一小時內存取超過 10 個 Secrets,標記為可疑
if time_span < timedelta(hours=1):
suspicious_activities.append({
'user': user,
'type': '短時間大量存取',
'secret_count': len(secrets),
'time_span': str(time_span),
'secrets': list(secrets)
})
# 模式 2: 存取與使用者角色不符的 Secrets
# 這需要預先定義使用者與其應該存取的 Secrets 的對應關係
# 此處僅為示例,實際實作需要與 RBAC 策略整合
return suspicious_activities
# 使用範例
with open('kubernetes-audit.log', 'r') as f:
logs = f.readlines()
suspicious = analyze_secret_access_patterns(logs)
for activity in suspicious:
print(f"可疑活動偵測:")
print(f" 使用者: {activity['user']}")
print(f" 類型: {activity['type']}")
print(f" 存取 Secret 數量: {activity['secret_count']}")
print(f" 時間範圍: {activity['time_span']}")
外部 Secret 服務的稽核能力
當企業使用外部 Secret 管理服務時,這些服務通常提供了更完善的稽核功能。例如,Google Cloud Secret Manager 會自動記錄所有對 Secrets 的存取,並與 Cloud Audit Logs 整合,企業可以在統一的介面中檢視所有 GCP 服務的稽核記錄。這些稽核日誌包含了詳細的上下文資訊,如存取者的 IP 位址、使用的身份驗證方式、請求的來源等,有助於進行深入的安全分析。
AWS Secrets Manager 與 AWS CloudTrail 整合,提供了完整的 API 呼叫記錄。企業可以設定 CloudTrail 將稽核日誌自動傳送到 S3 儲存桶,並設定生命週期規則來管理日誌的保留期限。對於需要長期保存稽核記錄以符合法規要求的企業,這種自動化的日誌管理功能可以大幅降低維運負擔。
Azure Key Vault 則提供了診斷日誌功能,可以記錄所有對 Key Vault 的操作。這些日誌可以輸出到 Azure Monitor、Azure Storage 或 Azure Event Hubs,讓企業可以根據自身需求選擇適當的日誌處理流程。Azure 還提供了 Microsoft Sentinel 作為安全資訊與事件管理系統,可以自動分析 Key Vault 的稽核日誌,偵測潛在的安全威脅。
# 使用 gcloud 命令查詢 Secret Manager 的稽核日誌
# 列出過去 7 天內所有對 Secrets 的存取記錄
# 設定專案 ID
PROJECT_ID="my-production-project"
# 查詢 Cloud Audit Logs
# 使用進階篩選條件找出所有 Secret Manager 相關的操作
gcloud logging read "
resource.type=\"secretmanager.googleapis.com/Secret\" AND
protoPayload.methodName=~\"google.cloud.secretmanager.*\" AND
timestamp >= \"$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)\"
" \
--project="${PROJECT_ID}" \
--format=json \
--limit=100 > secret_access_audit.json
# 分析稽核日誌,統計每個服務帳號的存取次數
echo "統計各服務帳號的 Secret 存取次數:"
cat secret_access_audit.json | \
jq -r '.protoPayload.authenticationInfo.principalEmail' | \
sort | uniq -c | sort -rn
# 找出存取失敗的記錄,可能表示有未授權的存取嘗試
echo "存取失敗的記錄:"
cat secret_access_audit.json | \
jq 'select(.protoPayload.status.code != 0)'
CI/CD 整合:在自動化流程中安全管理 Secrets
現代軟體開發已經無法脫離持續整合與持續部署的實踐,但 CI/CD 流程也帶來了 Secrets 管理的新挑戰。自動化部署需要存取各種敏感資訊如雲端憑證、資料庫密碼、API 金鑰等,如何在保持自動化效率的同時確保這些資訊的安全,是每個 DevOps 團隊必須面對的問題。傳統的做法是將 Secrets 硬編碼在 CI/CD 組態或腳本中,但這種方式存在嚴重的安全風險,一旦版本控制系統被入侵,所有敏感資訊都會洩漏。
GitOps 模式下的 Secrets 管理
GitOps 是一種流行的 CI/CD 模式,將基礎設施與應用程式的組態儲存在 Git 儲存庫中,透過 Pull Request 來管理變更,並由自動化工具如 ArgoCD 或 Flux 來同步組態到 Kubernetes 叢集。這種方式的優勢是所有變更都有完整的版本記錄與審核流程,但挑戰在於如何在 Git 中安全地儲存 Secrets。直接將明文的 Secrets 提交到 Git 是絕對不可接受的,即使是私有儲存庫也存在被入侵的風險。
Sealed Secrets 是解決這個問題的一種方案。它使用非對稱加密,讓開發者可以將加密後的 Secrets 安全地儲存在 Git 中,只有 Kubernetes 叢集中的控制器擁有私鑰可以解密這些 Secrets。這種設計確保即使 Git 儲存庫洩漏,攻擊者也無法取得實際的敏感資訊。Sealed Secrets 的工作流程是開發者使用公鑰加密 Secrets,將加密後的 SealedSecret 物件提交到 Git,ArgoCD 部署這個物件到叢集後,Sealed Secrets 控制器會自動解密並建立對應的 Secret 物件供應用程式使用。
# 安裝 Sealed Secrets 控制器到 Kubernetes 叢集
# 此控制器負責解密 SealedSecret 物件
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# 安裝 kubeseal 命令列工具,用於加密 Secrets
# macOS 使用者可以透過 Homebrew 安裝
brew install kubeseal
# 建立一個普通的 Secret YAML 檔案
cat <<EOF > database-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: database-credentials
namespace: production
type: Opaque
stringData:
username: admin
password: SuperSecurePassword123!
connection-string: postgresql://admin:SuperSecurePassword123!@db.company.com:5432/production
EOF
# 使用 kubeseal 加密 Secret
# 加密後的 SealedSecret 可以安全地提交到 Git 儲存庫
kubeseal --format=yaml < database-secret.yaml > database-sealed-secret.yaml
# 檢視加密後的內容
cat database-sealed-secret.yaml
# 將 SealedSecret 部署到叢集
# Sealed Secrets 控制器會自動解密並建立對應的 Secret
kubectl apply -f database-sealed-secret.yaml
# 驗證 Secret 已經被正確建立
kubectl get secret database-credentials -n production
另一種方法是使用 External Secrets Operator,它不將 Secrets 儲存在 Git 中,而是將 Secret 的參考資訊儲存在 Git,實際的敏感資料則儲存在外部的 Secret 管理服務中。當 External Secrets Operator 偵測到新的 ExternalSecret 物件時,會自動從指定的外部服務讀取實際的 Secret 值,並在 Kubernetes 中建立對應的 Secret 物件。這種方式結合了 GitOps 的組態管理優勢與外部 Secret 服務的安全性,是目前最被推薦的做法。
# External Secrets Operator 的組態範例
# 定義如何從 Google Cloud Secret Manager 同步 Secret 到 Kubernetes
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
# ExternalSecret 的名稱
name: database-credentials-sync
# 部署到 production 命名空間
namespace: production
spec:
# 設定同步間隔為 1 小時
# Secret Manager 中的值變更後,最多一小時會反映到 Kubernetes
refreshInterval: 1h
# 指定 Secret Store 的參考
secretStoreRef:
# Secret Store 的名稱,定義了如何連線到外部服務
name: gcpsm-secret-store
# Secret Store 的類型
kind: SecretStore
# 定義要建立的 Kubernetes Secret
target:
# 目標 Secret 的名稱
name: database-credentials
# Secret 的類型
creationPolicy: Owner
# 定義要同步哪些資料
data:
# 從 Secret Manager 讀取 database-username
- secretKey: username
remoteRef:
key: database-username
# 從 Secret Manager 讀取 database-password
- secretKey: password
remoteRef:
key: database-password
# 從 Secret Manager 讀取完整的連線字串
- secretKey: connection-string
remoteRef:
key: database-connection-string
---
# Secret Store 組態,定義如何連線到 Google Cloud Secret Manager
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: gcpsm-secret-store
namespace: production
spec:
provider:
# 使用 Google Cloud Secret Manager 作為提供者
gcpsm:
# GCP 專案 ID
projectID: "my-production-project"
# 使用 Workload Identity 進行身份驗證
# 這種方式不需要長期憑證,更加安全
auth:
workloadIdentity:
clusterLocation: asia-east1
clusterName: production-cluster
serviceAccountRef:
name: external-secrets-sa
Workload Identity:無密碼認證的最佳實務
傳統的 CI/CD 流程通常需要在 CI 系統中儲存長期有效的憑證,用於存取 Kubernetes 叢集或雲端服務。然而,這些長期憑證如果洩漏,攻擊者就可以長期存取企業的資源。Workload Identity 提供了一種更安全的替代方案,它允許工作負載使用短期的、自動輪換的令牌來進行身份驗證,大幅降低了憑證洩漏的風險。
GCP Workload Identity 與 GitHub Actions 整合實戰
在現代雲端架構中,Workload Identity 代表了身份驗證的典範轉移,從傳統的長期憑證管理轉向基於短期令牌的零信任模型。對於使用 Google Cloud Platform 與 GitHub Actions 的台灣企業而言,正確實作 Workload Identity 不僅能提升安全性,更能簡化憑證管理的複雜度,讓開發團隊專注在業務邏輯而非基礎設施的安全維護。本章節將深入探討如何建構完整的 Workload Identity 整合架構,從 GCP 端的基礎設施組態到 GitHub Actions 的工作流程設計,提供可立即應用的實務指南。
Workload Identity 的運作原理與架構設計
Workload Identity 的核心概念是利用 OpenID Connect 協定建立信任關係,讓外部系統如 GitHub Actions 可以在不儲存長期憑證的情況下,向 Google Cloud 證明自己的身份。當 GitHub Actions 執行時,它會從 GitHub 的 OIDC 提供者取得一個短期的 JWT 令牌,這個令牌包含了工作流程的上下文資訊如儲存庫名稱、分支、觸發事件等。GCP 的 Workload Identity Pool 會驗證這個令牌的簽章與內容,確認其來自可信任的 GitHub 環境,然後核發一個 GCP 的存取令牌,允許工作流程存取指定的 GCP 資源。
這種設計的安全優勢在於令牌的有效期限極短,通常只有數小時,即使令牌洩漏也無法被長期濫用。此外,企業可以透過屬性對映來實作精細的存取控制,例如只允許特定儲存庫的特定分支執行生產環境部署,其他分支或儲存庫則無法取得相應的權限。這種設計符合最小權限原則,大幅降低了因 CI/CD 環境被入侵而導致的安全風險。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
actor "開發者" as dev
participant "GitHub Actions" as gh
participant "GitHub OIDC Provider" as oidc
participant "GCP Workload Identity Pool" as pool
participant "GCP IAM" as iam
participant "Secret Manager" as sm
dev -> gh : 推送程式碼觸發工作流程
activate gh
gh -> oidc : 請求 OIDC 令牌
activate oidc
oidc --> gh : 回傳 JWT 令牌
deactivate oidc
gh -> pool : 使用 JWT 令牌請求存取
activate pool
pool -> pool : 驗證令牌簽章與內容
pool -> iam : 對映到服務帳號
activate iam
iam --> pool : 確認權限
deactivate iam
pool --> gh : 核發 GCP 存取令牌
deactivate pool
gh -> sm : 使用存取令牌讀取 Secrets
activate sm
sm -> iam : 驗證權限
iam --> sm : 授權存取
sm --> gh : 回傳 Secret 值
deactivate sm
gh -> gh : 執行部署作業
gh --> dev : 部署完成
deactivate gh
@enduml上述時序圖清晰展示了整個認證流程的互動過程。開發者推送程式碼後,GitHub Actions 自動啟動,首先向 GitHub 的 OIDC 提供者請求身份令牌,這個令牌經過 GCP Workload Identity Pool 的驗證後,會對映到預先設定的服務帳號,最終取得存取 Secret Manager 的權限。整個過程中沒有任何長期憑證參與,所有令牌都是臨時性的,大幅提升了安全性。
建構 Workload Identity Pool 與服務帳號架構
實作 Workload Identity 的第一步是在 GCP 中建立 Workload Identity Pool 與 Provider。Pool 代表一個信任域,用於集中管理外部身份提供者,而 Provider 則定義了具體的身份來源與對映規則。以 GitHub Actions 為例,我們需要建立一個 Provider 指向 GitHub 的 OIDC 端點,並設定屬性對映規則,將 GitHub 提供的聲明對映到 GCP 可以理解的屬性。
#!/bin/bash
# 建立與組態 Workload Identity Pool 的完整腳本
# 設定 GCP 專案 ID
# 請替換為實際的專案 ID
export PROJECT_ID="my-production-project"
# 設定 GitHub 組織或使用者名稱
export GITHUB_ORG="my-company"
# 設定 GitHub 儲存庫名稱
export GITHUB_REPO="production-app"
# 啟用必要的 GCP API
echo "啟用必要的 API 服務..."
gcloud services enable iamcredentials.googleapis.com \
secretmanager.googleapis.com \
sts.googleapis.com \
--project="${PROJECT_ID}"
# 建立 Workload Identity Pool
# 這個 Pool 將管理所有來自 GitHub Actions 的身份驗證請求
echo "建立 Workload Identity Pool..."
gcloud iam workload-identity-pools create "github-actions-pool" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="GitHub Actions Workload Identity Pool" \
--description="用於 GitHub Actions CI/CD 流程的身份驗證池"
# 建立 Workload Identity Provider
# 設定如何驗證來自 GitHub 的身份令牌
echo "建立 Workload Identity Provider..."
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="github-actions-pool" \
--display-name="GitHub OIDC Provider" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--allowed-audiences="https://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner == '${GITHUB_ORG}'"
# 取得專案編號,用於後續的權限設定
export PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")
# 建立專用的服務帳號
# 此服務帳號將被 GitHub Actions 使用來存取 GCP 資源
echo "建立服務帳號..."
gcloud iam service-accounts create github-actions-sa \
--project="${PROJECT_ID}" \
--display-name="GitHub Actions Service Account" \
--description="用於 GitHub Actions 存取 GCP 資源的服務帳號"
# 授予服務帳號存取 Secret Manager 的權限
echo "設定服務帳號權限..."
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member="serviceAccount:github-actions-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
# 允許特定的 GitHub 儲存庫假冒此服務帳號
# 這是 Workload Identity 的核心組態,建立外部身份與 GCP 服務帳號的信任關係
echo "綁定 Workload Identity..."
gcloud iam service-accounts add-iam-policy-binding \
"github-actions-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-actions-pool/attribute.repository/${GITHUB_ORG}/${GITHUB_REPO}"
# 輸出 Workload Identity Provider 的資源名稱
# 此資訊需要在 GitHub Actions 工作流程中使用
echo "Workload Identity Provider 資源名稱:"
echo "projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider"
echo "設定完成!請將上述 Provider 資源名稱加入到 GitHub Actions 工作流程中。"
上述腳本完成了 Workload Identity 所需的全部 GCP 端組態。關鍵的設定在於屬性對映與條件約束,透過 attribute-mapping 參數,我們將 GitHub 提供的聲明如 repository、actor 等對映到 GCP 的屬性系統,然後透過 attribute-condition 參數限制只有來自特定組織的儲存庫才能通過驗證。這種設計確保即使有人取得了 GitHub Actions 的令牌,也無法假冒不屬於該組織的身份。
設計安全的服務帳號權限架構
在建立服務帳號時,企業必須仔細考量權限的最小化原則。過度寬鬆的權限會增加安全風險,一旦 CI/CD 環境被入侵,攻擊者可能取得過多的存取權限。因此,建議為不同的部署環境建立不同的服務帳號,例如開發環境、測試環境、生產環境各自使用獨立的服務帳號,每個服務帳號只能存取對應環境的資源。
對於 Secret Manager 的存取,除了基本的 secretmanager.secretAccessor 角色外,企業還可以進一步限制服務帳號只能存取特定的 Secrets。這可以透過 Secret 層級的 IAM 策略來實作,為每個 Secret 單獨設定存取權限,而不是授予服務帳號全域的 Secret 存取權限。這種細緻的權限控制雖然增加了管理複雜度,但對於高度敏感的生產環境而言是必要的安全措施。
# 為特定 Secret 設定細緻的存取權限
# 而不是授予全域的 Secret 存取權限
# 建立開發環境專用的服務帳號
gcloud iam service-accounts create github-actions-dev-sa \
--project="${PROJECT_ID}" \
--display-name="GitHub Actions Dev Environment SA"
# 建立生產環境專用的服務帳號
gcloud iam service-accounts create github-actions-prod-sa \
--project="${PROJECT_ID}" \
--display-name="GitHub Actions Production Environment SA"
# 只允許開發環境服務帳號存取開發環境的 Secrets
gcloud secrets add-iam-policy-binding "dev-database-password" \
--project="${PROJECT_ID}" \
--member="serviceAccount:github-actions-dev-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
# 只允許生產環境服務帳號存取生產環境的 Secrets
gcloud secrets add-iam-policy-binding "prod-database-password" \
--project="${PROJECT_ID}" \
--member="serviceAccount:github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
# 設定生產環境服務帳號的額外限制
# 例如只允許 main 分支使用此服務帳號
gcloud iam service-accounts add-iam-policy-binding \
"github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-actions-pool/attribute.repository/${GITHUB_ORG}/${GITHUB_REPO}" \
--condition="expression=assertion.ref == 'refs/heads/main',title=only-main-branch,description=只允許 main 分支使用此服務帳號"
實作 GitHub Actions 工作流程
完成 GCP 端的設定後,接下來需要在 GitHub Actions 中組態工作流程來使用 Workload Identity。GitHub Actions 提供了官方的 google-github-actions/auth action,大幅簡化了認證流程的實作。這個 action 會自動處理 OIDC 令牌的取得與 GCP 存取令牌的交換,開發者只需要提供 Workload Identity Provider 的資源名稱與服務帳號即可。
# GitHub Actions 工作流程組態
# 展示如何使用 Workload Identity 安全地存取 GCP Secret Manager
name: Deploy to Production
# 定義觸發條件
on:
# 當推送到 main 分支時觸發
push:
branches:
- main
# 也可以手動觸發
workflow_dispatch:
# 定義環境變數
env:
# GCP 專案 ID
PROJECT_ID: my-production-project
# 部署的目標地區
REGION: asia-east1
jobs:
deploy:
# 工作名稱
name: Deploy Application
# 執行環境為最新的 Ubuntu
runs-on: ubuntu-latest
# 關鍵設定:授予 OIDC 令牌的寫入權限
# 這是使用 Workload Identity 的必要條件
permissions:
contents: read
id-token: write
steps:
# 步驟 1: 檢出程式碼
- name: Checkout code
uses: actions/checkout@v4
# 步驟 2: 使用 Workload Identity 進行 GCP 認證
# 這個步驟會自動處理 OIDC 令牌的取得與交換
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
# Workload Identity Provider 的完整資源路徑
# 這個值來自前面 GCP 組態腳本的輸出
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider'
# 要假冒的服務帳號
service_account: 'github-actions-prod-sa@my-production-project.iam.gserviceaccount.com'
# 令牌的有效期限,預設為 1 小時
token_format: 'access_token'
# 設定存取令牌的生命週期
access_token_lifetime: '3600s'
# 步驟 3: 設定 gcloud CLI
# 使用前一步驟取得的憑證來組態 gcloud
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
# 步驟 4: 從 Secret Manager 讀取部署所需的 Secrets
# 使用 gcloud 命令存取 Secret Manager
- name: Fetch secrets from Secret Manager
id: secrets
run: |
# 讀取資料庫連線字串
# 使用 --format 參數只輸出 Secret 的值,不包含其他資訊
DB_CONNECTION=$(gcloud secrets versions access latest \
--secret="prod-database-connection" \
--project="${PROJECT_ID}" \
--format='get(payload.data)' | base64 -d)
# 將 Secret 值設定為 GitHub Actions 的輸出變數
# 使用 GITHUB_OUTPUT 而不是舊的 set-output 命令
echo "database_connection=${DB_CONNECTION}" >> $GITHUB_OUTPUT
# 讀取 API 金鑰
API_KEY=$(gcloud secrets versions access latest \
--secret="prod-api-key" \
--project="${PROJECT_ID}" \
--format='get(payload.data)' | base64 -d)
echo "api_key=${API_KEY}" >> $GITHUB_OUTPUT
# 為了安全起見,在日誌中隱藏這些敏感值
echo "::add-mask::${DB_CONNECTION}"
echo "::add-mask::${API_KEY}"
# 步驟 5: 建立 Kubernetes Secret
# 使用從 Secret Manager 取得的值在 Kubernetes 中建立 Secret
- name: Create Kubernetes Secret
run: |
# 設定 kubectl 連線到 GKE 叢集
gcloud container clusters get-credentials production-cluster \
--region="${REGION}" \
--project="${PROJECT_ID}"
# 建立或更新 Kubernetes Secret
# 使用 --dry-run 和 apply 來實作 upsert 語義
kubectl create secret generic application-secrets \
--from-literal=database-connection="${{ steps.secrets.outputs.database_connection }}" \
--from-literal=api-key="${{ steps.secrets.outputs.api_key }}" \
--namespace=production \
--dry-run=client -o yaml | kubectl apply -f -
# 步驟 6: 部署應用程式
- name: Deploy application
run: |
# 更新 Kubernetes Deployment 來觸發滾動更新
kubectl rollout restart deployment/production-app \
--namespace=production
# 等待部署完成
kubectl rollout status deployment/production-app \
--namespace=production \
--timeout=5m
# 步驟 7: 驗證部署狀態
- name: Verify deployment
run: |
# 檢查 Pod 狀態
kubectl get pods --namespace=production -l app=production-app
# 檢查最近的事件,確認沒有錯誤
kubectl get events --namespace=production \
--sort-by='.lastTimestamp' \
--field-selector type!=Normal | tail -10
這個工作流程展示了完整的部署流程,從認證、讀取 Secrets、建立 Kubernetes 資源到最終的應用程式部署。關鍵的安全設計包括使用 add-mask 指令隱藏日誌中的敏感資訊,確保即使工作流程日誌被洩漏,也不會暴露實際的 Secret 值。此外,透過明確設定 permissions,限制工作流程只能取得必要的權限,符合最小權限原則。
進階安全措施與最佳實務
在生產環境中,除了基本的 Workload Identity 設定外,企業還應該實作額外的安全措施。第一是啟用 GitHub Actions 的環境保護規則,對於生產環境的部署,可以要求必須經過人工審核才能執行,避免惡意或錯誤的變更直接影響生產系統。第二是設定 GCP 的 VPC Service Controls,限制 Secret Manager 等敏感服務只能從特定的網路環境存取,即使攻擊者取得了有效的憑證,也無法從不受信任的位置存取資源。
對於多租戶環境,企業可以為每個團隊或專案建立獨立的 Workload Identity Pool,實作邏輯隔離。這種設計讓不同團隊的 CI/CD 流程完全獨立,一個團隊的安全事件不會影響其他團隊。此外,定期審查服務帳號的權限與使用記錄也是重要的安全實務,透過 Cloud Asset Inventory 與 Cloud Audit Logs,企業可以追蹤哪些服務帳號實際被使用,哪些已經不再需要但仍然擁有權限,及時清理過時的組態以降低攻擊面。
# 定期審查服務帳號使用情況的 Python 腳本
# 找出長期未使用的服務帳號,提醒管理員進行清理
from google.cloud import asset_v1, logging_v2
from datetime import datetime, timedelta
import json
def find_unused_service_accounts(project_id, days_threshold=90):
"""
找出指定天數內未使用的服務帳號
參數:
project_id: GCP 專案 ID
days_threshold: 多少天未使用視為閒置
"""
# 建立 Cloud Asset 客戶端
asset_client = asset_v1.AssetServiceClient()
# 建立 Cloud Logging 客戶端用於查詢稽核日誌
logging_client = logging_v2.Client(project=project_id)
# 列出專案中的所有服務帳號
parent = f"projects/{project_id}"
request = asset_v1.SearchAllResourcesRequest(
scope=parent,
asset_types=["iam.googleapis.com/ServiceAccount"],
)
# 計算閾值時間
threshold_date = datetime.now() - timedelta(days=days_threshold)
unused_accounts = []
# 遍歷所有服務帳號
for resource in asset_client.search_all_resources(request=request):
service_account_email = resource.name.split('/')[-1]
# 查詢此服務帳號的最近使用記錄
filter_str = f'''
protoPayload.authenticationInfo.principalEmail="{service_account_email}"
AND timestamp >= "{threshold_date.isoformat()}Z"
'''
# 檢查是否有使用記錄
entries = list(logging_client.list_entries(
filter_=filter_str,
max_results=1
))
# 如果沒有使用記錄,加入未使用列表
if not entries:
unused_accounts.append({
'email': service_account_email,
'display_name': resource.display_name,
'creation_time': resource.create_time,
'last_checked': datetime.now().isoformat()
})
print(f"發現閒置服務帳號: {service_account_email}")
print(f" 建立時間: {resource.create_time}")
print(f" 已超過 {days_threshold} 天未使用")
# 將結果輸出為 JSON 格式
report = {
'project_id': project_id,
'threshold_days': days_threshold,
'check_date': datetime.now().isoformat(),
'unused_accounts': unused_accounts,
'total_count': len(unused_accounts)
}
with open(f'unused_service_accounts_{project_id}.json', 'w') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
return unused_accounts
# 使用範例
if __name__ == '__main__':
project_id = 'my-production-project'
unused = find_unused_service_accounts(project_id, days_threshold=90)
if unused:
print(f"\n總計發現 {len(unused)} 個閒置服務帳號")
print("建議檢視這些帳號是否仍需要,若不需要請及時刪除")
else:
print("\n所有服務帳號在過去 90 天內都有使用記錄")
總結
Kubernetes Secrets 管理是一個涉及多個層面的複雜議題,從災難還原到加密防護,從稽核追蹤到 CI/CD 整合,每個環節都需要仔細規劃與實作。台灣企業在建立 Secrets 管理體系時,應該從自身的業務需求與合規要求出發,選擇適當的技術方案與實作策略。對於小型團隊,使用雲端服務商提供的託管 Secret 服務如 Google Cloud Secret Manager 或 AWS Secrets Manager 可能是最經濟的選擇,這些服務提供了企業級的安全性與可靠性,且不需要投入大量的維運資源。
對於大型企業或有特殊需求的組織,建立混合式的 Secrets 管理架構可能更為合適。例如將極度敏感的資料如加密金鑰儲存在本地的硬體安全模組中,而將應用層的 Secrets 儲存在雲端服務中,透過分層設計來平衡安全性與便利性。無論採用何種方案,定期的安全審查與演練都是必不可少的,企業應該建立 Secrets 輪換的標準流程,當懷疑 Secrets 可能洩漏時能夠快速更新所有相關系統。