Skip to content

Recipe: secret resolvers

Move API keys out of env vars and into Vault, AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager.

Vault (KV-v2)

import os
import tako

vault = tako.secrets.VaultResolver(
    addr=os.environ["VAULT_ADDR"],
    token=os.environ["VAULT_TOKEN"],
)

# Pull a single field from a KV-v2 secret object:
api_key = await vault.resolve("secret/data/myapp#openai_api_key")

provider = tako.providers.OpenAI(model="gpt-5", api_key=api_key)

AWS Secrets Manager

Reuses the AWS default credential chain (env, profile, IRSA, IMDS):

aws = tako.secrets.AwsSecretsManagerResolver(region="us-east-1")
api_key = await aws.resolve("prod/openai")          # latest version
api_key = await aws.resolve("prod/openai#abc123")  # specific version

Pin a profile or override the endpoint for VPC-private access:

aws = tako.secrets.AwsSecretsManagerResolver(
    profile_name="prod",
    endpoint_url="https://secretsmanager-vpce-foo.us-east-1.amazonaws.com",
)

Azure Key Vault

Auth deferred — supply a pre-resolved bearer token. For local dev:

export AZURE_KV_TOKEN="$(az account get-access-token \
    --resource https://vault.azure.net --query accessToken -o tsv)"
azure = tako.secrets.AzureKeyVaultResolver(
    "https://my-vault.vault.azure.net",
    os.environ["AZURE_KV_TOKEN"],
)
api_key = await azure.resolve("openai-api-key")          # latest
api_key = await azure.resolve("openai-api-key#abc123")  # version

For production, wire azure_identity::DefaultAzureCredential (or your own token-acquisition flow) and rebuild the resolver before tokens expire.

GCP Secret Manager

Same deferred-auth pattern as Vertex. Locally:

export GCP_TOKEN="$(gcloud auth print-access-token)"
gcp = tako.secrets.GcpSecretManagerResolver(
    "my-gcp-project",
    os.environ["GCP_TOKEN"],
)
api_key = await gcp.resolve("openai-api-key")    # latest
api_key = await gcp.resolve("openai-api-key#3")  # specific version number

The provider returns the value as a UTF-8 string, base64-decoding the SecretManager payload automatically.

Mixing resolvers

You can layer resolvers — for example, fall back from Vault to env vars during local dev:

async def resolve_with_fallback(key: str) -> str:
    try:
        return await vault.resolve(key)
    except ValueError:
        # Fall back to env var
        return os.environ[key]

There's no built-in "chain" resolver yet — Phase 3 will add one if demand surfaces.