| name | gcp-secret-manager |
| description | Secure secrets in Google Cloud Secret Manager. Configure IAM policies, integrate with GKE, and manage secret versions. Use when managing secrets in GCP environments. |
| license | MIT |
| metadata | {"author":"devops-skills","version":"1.0"} |
GCP Secret Manager
Store and manage secrets securely in Google Cloud Platform.
When to Use This Skill
Use this skill when:
- Managing secrets in GCP environments
- Integrating secrets with GKE workloads via Workload Identity
- Storing API keys, database credentials, or TLS certificates
- Implementing secret versioning and rotation
- Meeting compliance requirements for centralized secret management
Prerequisites
- GCP project with billing enabled
gcloud CLI installed and authenticated
- Secret Manager API enabled (
secretmanager.googleapis.com)
- IAM permissions:
roles/secretmanager.admin for management, roles/secretmanager.secretAccessor for reading
- For GKE: Workload Identity configured on the cluster
Enable the API
gcloud services enable secretmanager.googleapis.com
gcloud services list --enabled --filter="name:secretmanager"
Secret Creation and Management
gcloud secrets create db-password \
--replication-policy="automatic" \
--labels="env=production,team=platform"
echo -n "S3cur3P@ssw0rd!" | gcloud secrets versions add db-password --data-file=-
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"
gcloud secrets create regional-secret \
--replication-policy="user-managed" \
--locations="us-central1,us-east1"
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"
gcloud secrets versions access latest --secret=db-password
gcloud secrets versions access 3 --secret=db-password
echo -n "N3wS3cur3P@ss!" | gcloud secrets versions add db-password --data-file=-
gcloud secrets list --format="table(name, createTime, labels)"
gcloud secrets versions list db-password --format="table(name, state, createTime)"
gcloud secrets versions disable 1 --secret=db-password
gcloud secrets versions enable 1 --secret=db-password
gcloud secrets versions destroy 1 --secret=db-password
gcloud secrets delete db-password
gcloud secrets update db-password \
--expire-time=
gcloud secrets update temp-token \
--ttl=
gcloud secrets update db-password \
--update-labels=
gcloud secrets versions update 5 --secret=db-password --set-aliases=
IAM Bindings
gcloud secrets add-iam-policy-binding db-password \
--member="serviceAccount:myapp-sa@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
gcloud secrets add-iam-policy-binding db-password \
--member="serviceAccount:myapp-sa@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretVersionAccessor" \
--condition='expression=resource.name.endsWith("versions/latest"),title=latest-only'
gcloud secrets add-iam-policy-binding db-password \
--member="group:security-team@example.com" \
--role="roles/secretmanager.admin"
gcloud secrets get-iam-policy db-password
gcloud secrets remove-iam-policy-binding db-password \
--member="serviceAccount:old-sa@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:myapp-sa@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor" \
--condition='expression=resource.name.startsWith("projects/my-project/secrets/myapp-"),title=myapp-secrets-only'
Workload Identity for GKE
gcloud container clusters update my-cluster \
--zone us-central1-a \
--workload-pool=my-project.svc.id.goog
gcloud iam service-accounts create myapp-gke-sa \
--display-name="MyApp GKE Service Account"
gcloud secrets add-iam-policy-binding db-password \
--member="serviceAccount:myapp-gke-sa@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
gcloud iam service-accounts add-iam-policy-binding \
myapp-gke-sa@my-project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:my-project.svc.id.goog[production/myapp-sa]"
Kubernetes Manifests
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: production
annotations:
iam.gke.io/gcp-service-account: "myapp-gke-sa@my-project.iam.gserviceaccount.com"
---
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
---
Application SDK Examples
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) -> :
client = secretmanager.SecretManagerServiceClient()
parent =
secret_config = {
: {: {}},
}
labels:
secret_config[] = labels
secret = client.create_secret(
request={: parent, : secret_id, : secret_config}
)
client.add_secret_version(
request={: secret.name, : {: value.encode()}}
)
secret.name
() -> :
client = secretmanager.SecretManagerServiceClient()
parent =
version = client.add_secret_version(
request={: parent, : {: new_value.encode()}}
)
version.name
() -> :
client = secretmanager.SecretManagerServiceClient()
parent =
secrets = []
secret client.list_secrets(request={: parent, : filter_str}):
secrets.append({
: secret.name.split()[-],
: secret.create_time.isoformat(),
: (secret.labels),
})
secrets
creds = get_json_secret(, )
connection_string = (
)
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);
Secret Rotation with 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}")
Rotation Schedule with Cloud Scheduler
gcloud pubsub topics create secret-rotation
gcloud secrets update db-password \
--add-topics="projects/my-project/topics/secret-rotation" \
--event-types="SECRET_ROTATE"
gcloud secrets update db-password \
--next-rotation-time="2025-04-01T00:00:00Z" \
--rotation-period="2592000s"
Terraform Configuration
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}"
}
Troubleshooting
| Problem | Cause | Solution |
|---|
| "Secret Manager API not enabled" | API not activated in project | Run gcloud services enable secretmanager.googleapis.com |
| "Permission denied" on access | Missing secretAccessor role | Grant roles/secretmanager.secretAccessor on the specific secret |
| Workload Identity not working | K8s SA not bound to GCP SA | Verify annotation on K8s SA; check IAM binding with workloadIdentityUser |
| "Secret version is in DISABLED state" | Version was disabled | Enable with gcloud secrets versions enable VERSION --secret=SECRET |
| High latency on secret access | No client-side caching | Cache secrets in memory with TTL; use CSI driver for GKE |
| CMEK decrypt fails | KMS key permissions missing | Grant roles/cloudkms.cryptoKeyEncrypterDecrypter to Secret Manager SA |
| Rotation function not triggered | Pub/Sub topic not configured | Verify topic is attached to secret; check Cloud Function subscription |
Best Practices
- Use Workload Identity for GKE instead of exported service account keys
- Implement IAM least-privilege at the individual secret level, not project level
- Enable audit logging for all secret access (Cloud Audit Logs)
- Use secret versions for safe rollback during rotation issues
- Set expiration dates or TTLs on temporary secrets
- Integrate with Cloud KMS for customer-managed encryption keys
- Use labels consistently for organization and automation
- Monitor secret access patterns with Cloud Monitoring
- Implement rotation schedules for all long-lived credentials
- Use conditional IAM bindings to restrict access by resource name pattern
Related Skills