sondahub

sondahub / Google Secret Manager sandbox

A Google Secret Manager mock for the client libraries’ REST transport

Secret Manager’s v1 API, answered by sondahub: secrets global and regional, numbered versions you can disable and destroy, aliases and latest, payload checksums checked both ways, delayed destruction, expiry, rotation settings, labels, filters, etags and IAM policies — and a service account whose key file signs in here, so the official library runs with only its endpoint changed.

An independent imitation for testing. Not affiliated with, or endorsed by, Google.

Connect

Instead of
https://secretmanager.googleapis.com
API endpoint
https://api.sondahub.com
Transport
REST (HTTP/JSON)
Project
any — sondahub-demo in the examples; names come back with its number
Service account
https://api.sondahub.com/sandbox/google-secret-manager/service-account.json
Also at
https://api.sondahub.com/sandbox/google-secret-manager/v1
OpenAPI 3
https://api.sondahub.com/sandbox/google-secret-manager/openapi.json

Python (google-cloud-secret-manager)

from google.cloud import secretmanager
from google.oauth2 import service_account
from google.api_core.client_options import ClientOptions

# the sandbox's service account key file: https://api.sondahub.com/sandbox/google-secret-manager/service-account.json
creds = service_account.Credentials.from_service_account_file(
    'sondahub-service-account.json', scopes=['https://www.googleapis.com/auth/cloud-platform'])
client = secretmanager.SecretManagerServiceClient(
    credentials=creds, transport='rest', client_options=ClientOptions(api_endpoint='https://api.sondahub.com'))

# carry the sandbox session from each answer to the next request
http = client._transport._session   # the requests.Session the REST transport sends with
def keep(response, *args, **kwargs):
    token = response.headers.get('X-Sondahub-Session')
    if token:
        http.headers['X-Sondahub-Session'] = token
http.hooks['response'].append(keep)

parent = 'projects/sondahub-demo'
secret = client.create_secret(request={'parent': parent, 'secret_id': 'my-secret',
                                       'secret': {'replication': {'automatic': {}}}})
client.add_secret_version(request={'parent': secret.name, 'payload': {'data': b'hello world'}})
print(client.access_secret_version(request={'name': secret.name + '/versions/latest'}).payload.data)

Import it. In Sonda: Import → From a URL, paste the OpenAPI address. You get a folder per resource and a request per operation, with example bodies and names from the seed, so most requests work as they are. Then set the auth: any bearer token (sandbox-token works). Any client that imports OpenAPI 3 takes the same address.

Try it here

These examples share one session: run them in order and each sees what the one before it did.
List the secrets
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets" \
  -H "Authorization: Bearer sandbox-token"
Access the latest version
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/stripe-api-key/versions/latest:access" \
  -H "Authorization: Bearer sandbox-token"
Version 1 was destroyed
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/stripe-api-key/versions/1:access" \
  -H "Authorization: Bearer sandbox-token"
Create a secret
curl -X POST "https://api.sondahub.com/v1/projects/sondahub-demo/secrets?secretId=my-secret" \
  -H "Authorization: Bearer sandbox-token" \
  -H "Content-Type: application/json" \
  -d '{"replication":{"automatic":{}},"labels":{"env":"dev"}}'
Add a version (with its CRC32C)
curl -X POST "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/my-secret:addVersion" \
  -H "Authorization: Bearer sandbox-token" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"data":"aGVsbG8gd29ybGQ=","dataCrc32c":"3381945770"}}'
Access it
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/my-secret/versions/latest:access" \
  -H "Authorization: Bearer sandbox-token"
Filter by label
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets?filter=labels.env%3Dprod" \
  -H "Authorization: Bearer sandbox-token"

From the shell, keep the token yourself: curl -i shows X-Sondahub-Session; send it back with -H "X-Sondahub-Session: …". How sessions work.

The seed project

These answer in every project; what you create lives in the project (and location) you created it in.

SecretWhat it has
stripe-api-keyv1 destroyed, v2 disabled, v3 enabled; alias current → 3
db-passwordv1 enabled, v2 enabled; replicated to us-east1 and europe-west1; rotation every 30 days, notifying a topic
api-configv1 enabled; annotations
partner-api-keyv1 enabled; expires in two months
legacy-tokenv1 enabled, v2 disabled; destruction delayed 7 days; v2 is waiting it out
empty-secretno versions yet

Versions are numbered from 1. latest is the newest; an alias names one. Disabling stops access; destroying drops the data — or, with versionDestroyTtl on the secret, disables it now and destroys it when the delay runs out (enabling it in between cancels). A secret with expireTime or ttl deletes itself. Regional secrets live under /locations/{location} in 21 locations.

Every value in the seed is fake and made up when the site is built. What you write is kept in your own session token and nowhere else — but this is a public playground: never send it a real secret. Not affiliated with, or endorsed by, Google.

What it answers

POST/v1/projects/{project}/secrets?secretId=…Create; GET to list (pageSize, pageToken, filter).
GET/v1/projects/{project}/secrets/{secret}Get; PATCH with updateMask; DELETE with an optional etag.
POST/v1/projects/{project}/secrets/{secret}:addVersionpayload.data, payload.dataCrc32c.
GET/v1/projects/{project}/secrets/{secret}/versions/{version}:accessAnd …/versions to list, …/versions/{version} to get, :enable, :disable, :destroy.
POST/v1/projects/{project}/secrets/{secret}:setIamPolicyAnd :getIamPolicy, :testIamPermissions.
GET/v1/projects/{project}/locationsLocations; every path above also works under /locations/{location}.
POST/sandbox/google-secret-manager/tokenOAuth 2.0: the JWT-bearer grant (and refresh_token), for the published service account.

Errors are google.rpc.Status: {"error": {"code", "message", "status"}}. Enums come back as numbers when the client asks with $alt=json;enum-encoding=int, as the REST transports do.

Questions

Is this Google Cloud?

No — an independent imitation of Google Cloud Secret Manager’s v1 API for testing, not affiliated with or endorsed by Google. No project, billing account or KMS key is involved.

Will the Google client libraries work against it?

Yes, with their REST transport (transport='rest' in Python, the REST client in Go and Java, the fallback option in Node) and the API endpoint set to https://api.sondahub.com. The Python set-up on this page was run against the sandbox with google-cloud-secret-manager: the service-account sign-in, list, create, add versions with and without a checksum, access by number, latest and alias, disable, update with a mask, filters, delete and the IAM policy. The default gRPC transport can’t be served: a Cloudflare worker answers HTTP/1.1 and HTTP/2 requests, not native gRPC.

How does the sign-in work?

The service account in service-account.json ([email protected]) has a published key and a token_uri that points at the sandbox: google-auth signs a JWT with it and exchanges it there for an access token, as with Google — or, as the Secret Manager library does by default, sends a self-signed JWT straight away. The API takes any bearer token, so gcloud auth print-access-token output or any string works too; a request with none answers 401 UNAUTHENTICATED.

Are checksums checked?

Yes. A version added with dataCrc32c is refused if the CRC32C of the data doesn’t match, and every access answers with the payload’s CRC32C for the library to verify. clientSpecifiedPayloadChecksum says whether you sent one.

What is not modelled?

Customer-managed encryption keys (accepted, not used), Pub/Sub delivery of rotation and version events (the settings are kept and validated, nothing is published), managed rotation of Cloud SQL credentials, tags bound through Resource Manager, and the v1beta APIs. A secret keeps its newest 30 versions.