关于 gcp-secret-manager
面向在 Google Cloud Platform 中存储和管理机密的技能,涵盖 Secret Manager 配置、访问控制与版本管理。还提供 GKE Workload Identity、应用 SDK、轮换自动化和 Terraform 示例。
主要功能
- 01创建和管理机密及版本
- 02配置 IAM 访问绑定
- 03GKE Workload Identity 集成
- 04Python、Go 和 Node.js SDK 示例
- 05轮换自动化与 Terraform 配置
适用场景
- 01存储 API 密钥、数据库凭据或 TLS 证书
- 02让 GKE 工作负载访问机密
- 03为机密配置版本和轮换
- 04通过 IAM 控制机密访问
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ bagelhole/ devops-security-agent-skills --skill gcp-secret-manager安装后在 Claude Code 中输入/gcp-secret-manager确认已加载。
详细介绍
GCP Secret Manager
在 Google Cloud Platform 中安全地存储和管理机密。
何时使用此技能
在以下情况下使用此技能:
- 在 GCP 环境中管理机密
- 通过 Workload Identity 将机密集成到 GKE 工作负载中
- 存储 API 密钥、数据库凭据或 TLS 证书
- 实施机密版本管理和轮换
- 满足集中式机密管理的合规要求
前提条件
- 已启用结算功能的 GCP 项目
- 已安装并完成身份验证的
gcloudCLI - 已启用 Secret Manager API(
secretmanager.googleapis.com) - IAM 权限:管理机密需要
roles/secretmanager.admin,读取机密需要roles/secretmanager.secretAccessor - 对于 GKE:已在集群上配置 Workload Identity
启用 API
# Enable Secret Manager API
gcloud services enable secretmanager.googleapis.com
# Verify it's enabled
gcloud services list --enabled --filter="name:secretmanager"
创建和管理机密
# Create a secret (creates the secret resource, not the value)
gcloud secrets create db-password \
--replication-policy="automatic" \
--labels="env=production,team=platform"
# Add the secret value (first version)
echo -n "S3cur3P@ssw0rd!" | gcloud secrets versions add db-password --data-file=-
# Create secret with value in one command
echo -n '{"username":"dbadmin","password":"S3cur3P@ss!","host":"10.0.1.5","port":5432}' | \
gcloud secrets create db-credentials --data-file=- \
--replication-policy="automatic" \
--labels="env=production,team=platform"
# Create with specific region replication
gcloud secrets create regional-secret \
--replication-policy="user-managed" \
--locations="us-central1,us-east1"
# Create with customer-managed encryption key (CMEK)
gcloud secrets create sensitive-secret \
--replication-policy="user-managed" \
--locations="us-central1" \
--kms-key-name="projects/my-project/locations/us-central1/keyRings/my-ring/cryptoKeys/my-key"
# Access the latest version
gcloud secrets versions access latest --secret=db-password
# Access a specific version
gcloud secrets versions access 3 --secret=db-password
# Add a new version (rotation)
echo -n "N3wS3cur3P@ss!" | gcloud secrets versions add db-password --data-file=-
# List all secrets
gcloud secrets list --format="table(name, createTime, labels)"
# List versions of a secret
gcloud secrets versions list db-password --format="table(name, state, createTime)"
# Disable a version (makes it inaccessible but recoverable)
gcloud secrets versions disable 1 --secret=db-password
# Enable a disabled version
gcloud secrets versions enable 1 --secret=db-password
# Destroy a version (permanent)
gcloud secrets versions destroy 1 --secret=db-password
# Delete the entire secret
gcloud secrets delete db-password
# Set expiration on a secret
gcloud secrets update db-password \
--expire-time="2026-06-01T00:00:00Z"
# Set TTL-based expiration
gcloud secrets update temp-token \
--ttl="2592000s" # 30 days
# Update labels
gcloud secrets update db-password \
--update-labels="rotation=enabled,last-rotated=2025-01-15"
# Add version aliases
gcloud secrets versions update 5 --secret=db-password --set-aliases="production"
IAM 绑定
# Grant secret accessor role to a service account
gcloud secrets add-iam-policy-binding db-password \
--member="serviceAccount:[email protected]" \
--role="roles/secretmanager.secretAccessor"
# Grant access to a specific secret version
gcloud secrets add-iam-policy-binding db-password \
--member="serviceAccount:[email protected]" \
--role="roles/secretmanager.secretVersionAccessor" \
--condition='expression=resource.name.endsWith("versions/latest"),title=latest-only'
# Grant admin to security team
gcloud secrets add-iam-policy-binding db-password \
--member="group:[email protected]" \
--role="roles/secretmanager.admin"
# View IAM policy for a secret
gcloud secrets get-iam-policy db-password
# Remove access
gcloud secrets remove-iam-policy-binding db-password \
--member="serviceAccount:[email protected]" \
--role="roles/secretmanager.secretAccessor"
# Project-level IAM for all secrets
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:[email protected]" \
--role="roles/secretmanager.secretAccessor" \
--condition='expression=resource.name.startsWith("projects/my-project/secrets/myapp-"),title=myapp-secrets-only'
GKE 的 Workload Identity
# Enable Workload Identity on cluster (if not already)
gcloud container clusters update my-cluster \
--zone us-central1-a \
--workload-pool=my-project.svc.id.goog
# Create GCP service account for the workload
gcloud iam service-accounts create myapp-gke-sa \
--display-name="MyApp GKE Service Account"
# Grant secret accessor role
gcloud secrets add-iam-policy-binding db-password \
--member="serviceAccount:[email protected]" \
--role="roles/secretmanager.secretAccessor"
# Bind Kubernetes SA to GCP SA
gcloud iam service-accounts add-iam-policy-binding \
[email protected] \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:my-project.svc.id.goog[production/myapp-sa]"
Kubernetes 清单
# Kubernetes service account annotated with GCP SA
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: production
annotations:
iam.gke.io/gcp-service-account: "[email protected]"
---
# Secrets Store CSI Driver for GCP
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: gcp-secrets
namespace: production
spec:
provider: gcp
parameters:
secrets: |
- resourceName: "projects/my-project/secrets/db-password/versions/latest"
path: "db-password"
- resourceName: "projects/my-project/secrets/db-credentials/versions/latest"
path: "db-credentials"
- resourceName: "projects/my-project/secrets/api-key/versions/latest"
path: "api-key"
secretObjects:
- secretName: myapp-secrets
type: Opaque
data:
- objectName: db-password
key: DB_PASSWORD
- objectName: api-key
key: API_KEY
---
# Deployment using the secrets
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: gcr.io/my-project/myapp:v1.0.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: DB_PASSWORD
volumeMounts:
- name: secrets
mountPath: "/var/secrets"
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "gcp-secrets"
应用 SDK 示例
Python
from google.cloud import secretmanager
from google.api_core import exceptions
import json
def get_secret(project_id: str, secret_id: str, version: str = "latest") -> str:
"""Access a secret version from GCP Secret Manager."""
client = secretmanager.SecretManagerServiceClient()
name = f"projects/{project_id}/secrets/{secret_id}/versions/{version}"
try:
response = client.access_secret_version(request={"name": name})
return response.payload.data.decode("UTF-8")
except exceptions.NotFound:
raise ValueError(f"Secret {secret_id} version {version} not found")
except exceptions.PermissionDenied:
raise PermissionError(f"No access to secret {secret_id}")
def get_json_secret(project_id: str, secret_id: str) -> dict:
"""Access and parse a JSON secret."""
raw = get_secret(project_id, secret_id)
return json.loads(raw)
def create_secret(project_id: str, secret_id: str, value: str, labels: dict = None) -> str:
"""Create a new secret with an initial version."""
client = secretmanager.SecretManagerServiceClient()
parent = f"projects/{project_id}"
secret_config = {
"replication": {"automatic": {}},
}
if labels:
secret_config["labels"] = labels
secret = client.create_secret(
request={"parent": parent, "secret_id": secret_id, "secret": secret_config}
)
client.add_secret_version(
request={"parent": secret.name, "payload": {"data": value.encode("UTF-8")}}
)
return secret.name
def rotate_secret(project_id: str, secret_id: str, new_value: str) -> str:
"""Add a new version to rotate the secret."""
client = secretmanager.SecretManagerServiceClient()
parent = f"projects/{project_id}/secrets/{secret_id}"
version = client.add_secret_version(
request={"parent": parent, "payload": {"data": new_value.encode("UTF-8")}}
)
return version.name
def list_secrets(project_id: str, filter_str: str = "") -> list:
"""List all secrets in a project."""
client = secretmanager.SecretManagerServiceClient()
parent = f"projects/{project_id}"
secrets = []
for secret in client.list_secrets(request={"parent": parent, "filter": filter_str}):
secrets.append({
"name": secret.name.split("/")[-1],
"created": secret.create_time.isoformat(),
"labels": dict(secret.labels),
})
return secrets
# Usage
creds = get_json_secret("my-project", "db-credentials")
connection_string = (
f"postgresql://{creds['username']}:{creds['password']}"
f"@{creds['host']}:{creds['port']}/mydb"
)
Go
package main
import (
"context"
"fmt"
"log"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
secretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
)
func getSecret(projectID, secretID, version string) (string, error) {
ctx := context.Background()
client, err := secretmanager.NewClient(ctx)
if err != nil {
return "", fmt.Errorf("failed to create client: %w", err)
}
defer client.Close()
name := fmt.Sprintf("projects/%s/secrets/%s/versions/%s", projectID, secretID, version)
result, err := client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
Name: name,
})
if err != nil {
return "", fmt.Errorf("failed to access secret: %w", err)
}
return string(result.Payload.Data), nil
}
func main() {
secret, err := getSecret("my-project", "db-password", "latest")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Secret: %s\n", secret)
}
Node.js
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
async function getSecret(projectId, secretId, version = 'latest') {
const name = `projects/${projectId}/secrets/${secretId}/versions/${version}`;
const [response] = await client.accessSecretVersion({ name });
return response.payload.data.toString('utf8');
}
async function main() {
const password = await getSecret('my-project', 'db-password');
console.log(`Secret retrieved, length: ${password.length}`);
}
main().catch(console.error);
使用 Cloud Functions 轮换机密
"""cloud_function_rotation.py - Triggered by Pub/Sub on secret rotation events."""
import functions_framework
from google.cloud import secretmanager
import secrets
import string
@functions_framework.cloud_event
def rotate_secret(cloud_event):
"""Handle secret rotation events from Pub/Sub."""
data = cloud_event.data
secret_name = data.get("name", "")
if "db-password" not in secret_name:
print(f"Skipping non-DB secret: {secret_name}")
return
client = secretmanager.SecretManagerServiceClient()
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
new_password = ''.join(secrets.choice(alphabet) for _ in range(32))
parent = "/".join(secret_name.split("/")[:4])
client.add_secret_version(
request={
"parent": parent,
"payload": {"data": new_password.encode("UTF-8")},
}
)
print(f"Rotated secret: {parent}")
使用 Cloud Scheduler 设置轮换计划
# Create a Pub/Sub topic for rotation events
gcloud pubsub topics create secret-rotation
# Configure secret to publish rotation events
gcloud secrets update db-password \
--add-topics="projects/my-project/topics/secret-rotation" \
--event-types="SECRET_ROTATE"
# Set up rotation schedule
gcloud secrets update db-password \
--next-rotation-time="2025-04-01T00:00:00Z" \
--rotation-period="2592000s" # 30 days
Terraform 配置
resource "google_secret_manager_secret" "db_password" {
project = var.project_id
secret_id = "db-password"
replication {
auto {}
}
labels = {
env = "production"
team = "platform"
}
rotation {
next_rotation_time = "2025-04-01T00:00:00Z"
rotation_period = "2592000s"
}
topics {
name = google_pubsub_topic.secret_rotation.id
}
}
resource "google_secret_manager_secret_version" "db_password" {
secret = google_secret_manager_secret.db_password.id
secret_data = var.db_password
}
resource "google_secret_manager_secret_iam_member" "app_accessor" {
project = var.project_id
secret_id = google_secret_manager_secret.db_password.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.app.email}"
}
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| “Secret Manager API not enabled” | 项目中未启用 API | 运行 gcloud services enable secretmanager.googleapis.com |
| 访问时出现“Permission denied” | 缺少 secretAccessor 角色 | 在特定机密上授予 roles/secretmanager.secretAccessor |
| Workload Identity 无法正常工作 | K8s SA 未绑定到 GCP SA | 验证 K8s SA 上的注释;检查带有 workloadIdentityUser 的 IAM 绑定 |
| “Secret version is in DISABLED state” | 版本已停用 | 使用 gcloud secrets versions enable VERSION --secret=SECRET 启用 |
| 访问机密时延迟较高 | 未进行客户端缓存 | 在内存中缓存机密并设置 TTL;在 GKE 中使用 CSI 驱动程序 |
| CMEK 解密失败 | 缺少 KMS 密钥权限 | 向 Secret Manager SA 授予 roles/cloudkms.cryptoKeyEncrypterDecrypter |
| 未触发轮换函数 | 未配置 Pub/Sub 主题 | 验证主题是否已关联到机密;检查 Cloud Function 订阅 |
最佳实践
- 对于 GKE,使用 Workload Identity,而不是导出的服务账号密钥
- 在单个机密级别而非项目级别实施 IAM 最小权限原则
- 为所有机密访问启用审计日志记录(Cloud Audit Logs)
- 在轮换出现问题时,使用机密版本安全回滚
- 为临时机密设置到期日期或 TTL
- 与 Cloud KMS 集成,以使用客户管理的加密密钥
- 统一使用标签,以便组织管理和自动化
- 使用 Cloud Monitoring 监控机密访问模式
- 为所有长期有效的凭据设置轮换计划
- 使用条件 IAM 绑定,根据资源名称模式限制访问
相关技能
- hashicorp-vault - 多云机密管理
- gcp-gke - GKE 集成
- aws-secrets-manager - AWS 机密管理
- azure-keyvault - Azure 机密管理
评论
支持 Markdown