Skip to content

Google Cloud Secret Manager Provider

The Google Cloud Secret Manager provider integrates with GCP for centralized secret management.

Providergcsm
URIgcsm://PROJECT_ID
AccessRead and write; secret references are read-only
Best forWorkloads and teams on Google Cloud
AuthenticationGoogle Application Default Credentials
Build featuregcsm
Default storagesecretspec2--{project}--{profile}--{key} (0.20+)
Terminal window
# Set a secret
$ secretspec set DATABASE_URL --provider gcsm://my-gcp-project
Enter value for DATABASE_URL: postgresql://localhost/mydb
Secret 'DATABASE_URL' saved to gcsm (profile: default)
# Run with secrets
$ secretspec run --provider gcsm://my-gcp-project -- npm start
  • Google Cloud CLI (gcloud)
  • GCP project with Secret Manager API enabled
  • Build with --features gcsm

Google Cloud Secret Manager uses Application Default Credentials. For local development:

Terminal window
$ gcloud auth application-default login

In Google Cloud runtimes, Application Default Credentials use the attached service account automatically.

gcsm://PROJECT_ID
  • PROJECT_ID: Your GCP project ID
gcsm://my-gcp-project
secretspec.toml
[providers]
google = "gcsm://my-gcp-project"
[profiles.production]
DATABASE_URL = { description = "Database URL", providers = ["google"] }

SecretSpec joins the project, profile, and key with validated -- boundaries. Distinct logical addresses therefore cannot collapse onto one GCSM secret when a project or profile contains a single internal hyphen. For example, project myapp, profile production, and key DATABASE_URL map to:

secretspec2--myapp--production--DATABASE_URL

Each component may contain ASCII letters, digits, underscores, and single internal hyphens. A component cannot start or end with - or contain --, because those forms could overlap a boundary. The complete GCSM id must fit the service’s 255-character limit.

Releases through 0.19 accepted project, profile, and key names the new layout cannot represent, such as a project directory named my--app. Reads of such an address keep serving the 0.19 secret and print a warning, but writes fail until the name changes. Rename the offending component and run secretspec set to store the value under the new id, or address the secret with an explicit ref, which is exempt from the convention.

SecretSpec 0.20 reads the new id first. When that secret holds no value, the read falls back to the 0.19 secretspec-{project}-{profile}-{key} id and returns its latest value, printing one warning per run. A project upgraded from 0.19 therefore keeps working with no migration step.

With secret-level IAM, an unbound new id can return PERMISSION_DENIED instead of NOT_FOUND. SecretSpec still probes the legacy id in that case and uses it when readable. If the legacy id supplies no value, the original denial remains an error; failures other than the expected permission denial from a legacy-id probe are also reported rather than treated as a missing secret.

The fallback is a read. Nothing is created, copied, or deleted, so the upgrade needs no new permissions: credentials holding only roles/secretmanager.secretAccessor, the usual CI principal, keep working unchanged.

Writes always use the new id. Running secretspec set for a secret is what moves it, and afterwards reads stop consulting the legacy id. The 0.19 secret is left in place, so an older SecretSpec keeps reading the value it knows and a rollback needs no recovery step.

Two consequences are worth planning for:

  • A secret still served by the fallback depends on the 0.19 id continuing to exist. Delete legacy secrets only after the values that matter have been written under the new id.
  • While a secret is served by the fallback, a 0.19 writer and a 0.20 writer update different ids. Point every writer at the same SecretSpec version, or set the secret with 0.20 to settle it on the new id.

Only the value is read across. Labels, rotation settings, secret-level IAM bindings, and other resource metadata belong to the legacy secret; reproduce any such configuration when you write the secret under its new id. If the legacy id had already received writes from colliding logical addresses, the provider cannot determine which historical version belonged to which address.

An explicit ref is a native address and is never renamed or migrated:

[profiles.production]
DATABASE_URL = {
description = "DB",
ref = { item = "secretspec-myapp-production-DATABASE_URL" },
providers = ["google"]
}

A secret’s ref field names an existing secret instead: item is the secret id, and the optional version pins a version (defaults to latest; field is not supported). References are read-only in this provider.

[profiles.production]
DATABASE_URL = { description = "DB", ref = { item = "database-url" }, providers = ["gcsm://my-gcp-project"] }
SIGNING_KEY = { description = "Key", ref = { item = "signing-key", version = "3" }, providers = ["gcsm://my-gcp-project"] }
Terminal window
# Set credentials
$ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
# Run command
$ secretspec run --provider gcsm://my-gcp-project -- deploy