Phase 2b — `ToolUp.Secrets.GcpSecretManager` companion
Phase 2b — ToolUp.Secrets.GcpSecretManager companion
What changes
A new cloud-secret-manager companion ships at src/Secrets/GcpSecretManager/. It implements ISecretStore over Google Cloud Secret Manager via the Google.Cloud.SecretManager.V1 SDK, closing the GCP gap in Phase 2a (which shipped Azure Key Vault, AWS Secrets Manager, and HashiCorp Vault). Deployments targeting Cloud Run / GKE / GCE / App Engine can now wire a managed-secret store without falling back to EncryptedSecretStore over FileSecretStore.
The composition root's existing TOOLUP_SECRET_STORE env-driven switch gains one new value, gcp-secret-manager. The SDK's EncryptedSecretStoreModeValidator (Phase 6l.E) recognises that value as cloud-KMS-backed and skips the master-key requirement, matching the existing Azure / AWS / Vault behaviour. The SDK's SecretStore.fromEnv resolver helper is unchanged in shape — consumers thread one additional CloudSecretStoreResolver entry through the helper to wire the companion in.
No consumer-side behaviour changes by default: the new companion is opt-in via the env switch + <PackageReference>.
Diff to apply
Directory.Packages.props (CPM consumer)
Add the companion to the consumer's Directory.Packages.props if it doesn't already arrive via the ToolUp.Sdk meta-manifest:
<PackageVersion Include="ToolUp.Secrets.GcpSecretManager" Version="$(ToolUpSdkVersion)" />
Consumer's Server.fsproj (or wherever cloud-companion PackageReferences live)
Add the package reference next to the existing cloud-secret-manager companions:
<ItemGroup>
<PackageReference Include="ToolUp.Secrets.AzureKeyVault" />
<PackageReference Include="ToolUp.Secrets.AwsSecretsManager" />
<PackageReference Include="ToolUp.Secrets.HashiCorpVault" />
+ <PackageReference Include="ToolUp.Secrets.GcpSecretManager" />
</ItemGroup>
Consumer's composition root (e.g. Server.fs)
Add the GCP resolver to the CloudSecretStoreResolver list passed into SecretStore.fromEnv:
open ToolUp.Platform.SecretStore
let secretStore =
SecretStore.fromEnv logger [
{ Name = "azure-key-vault"; Resolve = ToolUp.Secrets.AzureKeyVault.fromEnv }
{ Name = "aws-secrets-manager"; Resolve = ToolUp.Secrets.AwsSecretsManager.fromEnv }
{ Name = "vault"; Resolve = ToolUp.Secrets.HashiCorpVault.fromEnv }
+ { Name = "gcp-secret-manager"; Resolve = ToolUp.Secrets.GcpSecretManager.fromEnv }
]
The resolver list is order-independent (matched by Name case-insensitively); append at the tail or wherever feels natural alongside the existing three.
Deployment environment
Set the activating env vars in the deployment's environment configuration:
TOOLUP_SECRET_STORE=gcp-secret-manager
TOOLUP_GCP_PROJECT_ID=<your-gcp-project-id>
On Cloud Run / GKE / GCE / App Engine, ADC auto-resolves to the attached workload-identity-bound service account — no additional env vars are needed. Off-GCP (dev shells, cross-cloud deploys) additionally set GOOGLE_APPLICATION_CREDENTIALS to a service-account JSON key file path.
The service account needs:
roles/secretmanager.secretAccessor— read (secretmanager.versions.access)roles/secretmanager.secretVersionAdder— write (secretmanager.secrets.create+secretmanager.versions.add)- A custom role granting
secretmanager.secrets.delete+secretmanager.secrets.list— or useroles/secretmanager.adminfor full access.
Full IAM detail in the companion's README.
Verification steps
After applying the diff above:
dotnet restore— confirms the new<PackageReference>resolves cleanly against the configured feed.dotnet build— confirms the resolver list still type-checks (the newCloudSecretStoreResolverentry must satisfy the record type).- Startup log diff — with
TOOLUP_SECRET_STORE=gcp-secret-manager+TOOLUP_GCP_PROJECT_IDset, the SDK'sSecretStore.fromEnvhelper emitsSecret store: gcp-secret-manager(Info-level). Without the env vars set, the deployment continues to use whatever default the composition root chose (typicallyEncryptedSecretStoreoverFileSecretStore). EncryptedSecretStoreModeValidatorpasses withTOOLUP_SECRETS_MASTER_KEYunset whenTOOLUP_SECRET_STORE=gcp-secret-manager— the validator recognises the new value as cloud-KMS-backed and skips the master-key gate. Verify in theHealthMonitorUIadmin tab or/dev/inspectValidators panel.- End-to-end smoke test: with a real GCP project + service account configured, write a secret via
secretStore.SetSecret("_platform", "smoke-test", "hello")and read it back viasecretStore.GetSecret("_platform", "smoke-test")— should returnSome "hello". ThenDeleteSecretand re-GetSecret— should returnNone. - Contract test pack: with
TOOLUP_GCP_PROJECT_IDset against a CI-dedicated project, theISecretStoreContractpack passes against the new binding atsrc/ToolUp.Platform.Tests/InProcess/GcpSecretManagerSecretStoreTests.fs. Without the env var the pack emits a single skipped (pending) test rather than green-by-default — a missing CI-side project ID is visible in the test report.
Rollback
Remove the <PackageReference> and the resolver-list entry; unset TOOLUP_SECRET_STORE (or set it to encrypted / one of the other cloud values). The composition root falls back to its prior store choice without further code change. No data migration required — secrets stored via the companion live in GCP Secret Manager, not the SDK's blob storage; reverting the composition leaves them in GCP untouched.
Risks
- Version-pin breakage on
name@versionIdkeys. Callers passing keys containing a literal@will see surprising behaviour because the companion treats@as the version-pin separator (e.g.GetSecret "email@example.com"parses to(name="email", version="example.com")). The other cloud companions don't reserve@as a separator; consumers porting keys across vendors should normalise or avoid embedded@. - Project-ID conflation.
TOOLUP_GCP_PROJECT_IDis read at composition time; rotating between projects requires a restart. Mirrors AWS region / Azure vault URL behaviour. - No soft-delete. Unlike Azure Key Vault (90-day soft-delete) and AWS Secrets Manager (7-30 day scheduled deletion), GCP Secret Manager removes secrets irreversibly. There is no recovery path for an accidental
DeleteSecret. Operators relying on the Azure / AWS soft-delete safety net should establish backup or audit-log-based recovery procedures before depending on the GCP companion in production.