I needed to check what was in a GCP secret to debug why an application wasn’t starting. Here’s how we inject secrets into GKE workloads, plus some CLI commands for debugging.
Debugging secrets with gcloud Link to heading
These commands are useful for checking secret values during debugging - not how you’d access them in production.
List all secrets:
gcloud secrets list
Filter by name:
gcloud secrets list | grep MY_SECRET
Get the latest version of a secret:
gcloud secrets versions access latest \
--secret="MY_SECRET" \
--project my-project
Get a specific version:
gcloud secrets versions access 3 \
--secret="MY_SECRET" \
--project my-project
List versions of a secret:
gcloud secrets versions list MY_SECRET --project my-project
Be careful - this prints the secret to your terminal. Don’t do it if someone’s looking over your shoulder.
How to inject secrets into applications Link to heading
Reading secrets via gcloud is fine for debugging, but not how you should get secrets into production apps. Here are the methods I actually use:
1. Environment variables (simplest) Link to heading
For Cloud Run:
gcloud run deploy my-service \
--set-secrets=DATABASE_URL=my-secret:latest
For GKE, use the Secret Manager CSI driver. It injects secrets directly into pods without storing them in etcd - much better from a security perspective.
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: app-secrets
spec:
provider: gcp
parameters:
secrets: |
- resourceName: "projects/my-project/secrets/database-url/versions/latest"
path: "database-url"
Then mount it in your pod:
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "app-secrets"
Both the CSI driver and kube-secrets-init use Workload Identity to fetch secrets - no service account keys needed.
Another nice thing about the CSI driver: it can sync secrets periodically. If your application watches the mounted file for changes, you get secret rotation without pod restarts.
Why CSI driver over alternatives:
- Secret Manager CSI Driver - Secrets injected directly via Workload Identity, never stored in etcd. Supports periodic sync for rotation. The right choice for GKE.
- External Secrets Operator - Syncs to Kubernetes Secrets which end up in etcd. Avoid for sensitive data.
- kube-secrets-init - Also uses Workload Identity, but no longer actively developed. Migrate away if you’re using it.
2. Volume mounts (for files) Link to heading
Sometimes you need the secret as a file (like a service account key or TLS cert). Mount it:
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "app-secrets"
3. Application code (most flexible) Link to heading
For Python apps, the Secret Manager client library works well:
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
name = f"projects/my-project/secrets/my-secret/versions/latest"
response = client.access_secret_version(request={"name": name})
secret = response.payload.data.decode("UTF-8")
This lets you fetch secrets on-demand and cache them in memory. Good for secrets that change infrequently but might need to be rotated without redeploying.
Secret rotation workflow Link to heading
We rotate secrets quarterly (or immediately if compromised). Here’s the process:
Create new version:
echo -n "new-secret-value" | gcloud secrets versions add my-secret --data-file=-Test with new version: Update one service to use the new version explicitly:
gcloud run deploy my-service \ --set-secrets=DATABASE_URL=my-secret:2Roll out to all services: Once confirmed working, update services to use
latest:gcloud run deploy my-service \ --set-secrets=DATABASE_URL=my-secret:latestDisable old version: Wait a few days, then disable (not delete) the old version:
gcloud secrets versions disable 1 --secret=my-secretDelete after retention period: After 30 days (or your compliance requirement), delete:
gcloud secrets versions destroy 1 --secret=my-secret
The key is disabling before deleting. You can re-enable if something breaks.
Common mistakes Link to heading
Mistake 1: Using version 1 forever
Always use latest in production unless you have a specific reason not to. Otherwise rotation requires updating every service.
Mistake 2: Not setting up alerts
Set up an alert for secret access failures:
gcloud logging metrics create secret-access-failures \
--description="Failed secret accesses" \
--log-filter='resource.type="secretmanager.googleapis.com/Secret" severity>=ERROR'
Mistake 3: Over-sharing secrets
Don’t give roles/secretmanager.admin to service accounts. They only need roles/secretmanager.secretAccessor for specific secrets:
gcloud secrets add-iam-policy-binding my-secret \
--member=serviceAccount:[email protected] \
--role=roles/secretmanager.secretAccessor