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
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets" \ -H "Authorization: Bearer sandbox-token"
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/stripe-api-key/versions/latest:access" \ -H "Authorization: Bearer sandbox-token"
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/stripe-api-key/versions/1:access" \ -H "Authorization: Bearer sandbox-token"
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"}}'
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"}}'
curl "https://api.sondahub.com/v1/projects/sondahub-demo/secrets/my-secret/versions/latest:access" \ -H "Authorization: Bearer sandbox-token"
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.
| Secret | What it has |
|---|---|
stripe-api-key | v1 destroyed, v2 disabled, v3 enabled; alias current → 3 |
db-password | v1 enabled, v2 enabled; replicated to us-east1 and europe-west1; rotation every 30 days, notifying a topic |
api-config | v1 enabled; annotations |
partner-api-key | v1 enabled; expires in two months |
legacy-token | v1 enabled, v2 disabled; destruction delayed 7 days; v2 is waiting it out |
empty-secret | no 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
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.