Skip to content

Vault policies

HashiCorp Vault is the recommended secret store for Alquimia Platform. Well-designed Vault policies let the runtime resolve credentials with the least privilege required, support rotation, and keep audit records for every secret access.

The runtime can authenticate to Vault in several ways:

MethodUse case
TokenLong-lived static token; suitable for local development, discouraged in production.
KubernetesRuntime pods authenticate using projected service-account tokens.
JWTWorkloads or agents authenticate using SPIFFE JWT-SVIDs.

Production deployments should use Kubernetes auth for runtime services and JWT auth for per-agentspace agent identities.

The runtime uses a pluggable secret resolver selected by ALQUIMIA_REGISTRY_SECRET_RESOLVER:

ResolverValueUse case
Vaultvault (default)Production; dynamic credentials, audit logging, central rotation
EnvironmentenvLocal development, CI tests, or air-gapped environments

When ALQUIMIA_REGISTRY_SECRET_RESOLVER=vault, the runtime reads:

VariableDescription
VAULT_ADDRFull Vault URL including scheme and port, for example https://vault.internal:8200 (used verbatim)
VAULT_MOUNT_POINTKV v2 mount point (default secret). cubbyhole does not work because it is a per-token store
VAULT_AUTH_METHODtoken (default), kubernetes, or jwt
VAULT_AUTH_ROLEVault role the resolver logs in with; required for kubernetes and jwt auth
VAULT_TOKENStatic token; only used with VAULT_AUTH_METHOD=token (local development)

With Kubernetes auth, the runtime’s own Vault client and the alquimia-core resolver log in separately: set both VAULT_K8S_ROLE (runtime) and VAULT_AUTH_ROLE (resolver), and mount a projected service-account token with audience vault (point VAULT_K8S_TOKEN_PATH at it). The static VAULT_TOKEN and the alquimia-vault Kubernetes Secret were removed from the manifests in favour of Kubernetes auth.

Secret references in agent specs are resolved to KV v2 paths based on their scope:

ScopeKey formatVault path
globalKEYsecret/data/global/KEY
shared{REALM}_KEYsecret/data/{realm}/shared/KEY
local{REALM}_{ASSISTANT_ID}_KEYsecret/data/{realm}/local/{assistant_id}/KEY

REALM is the agentspace_id.

As of alquimia-core 0.5.5, local and shared secrets support per-user overrides. When a user_id is present, the resolver first tries the user-specific path and falls back to the scope default. global secrets never have per-user overrides.

ScopePer-user key formatPer-user Vault path
shared{REALM}_USER_{USER_ID}_{KEY}secret/data/{realm}/shared/users/{user_id}/KEY
local{REALM}_{ASSISTANT_ID}_USER_{USER_ID}_{KEY}secret/data/{realm}/local/{assistant_id}/users/{user_id}/KEY

When ALQUIMIA_REGISTRY_SECRET_RESOLVER=env, the runtime reads secret values directly from environment variables. The same key formats apply:

Terminal window
export RESPONSE_PROVIDER_API_KEY="sk-..." # global
export DEFAULT_OPENAI_API_KEY="sk-..." # shared (realm=default)
export DEFAULT_SUPPORT_BOT_RESPONSE_PROVIDER_API_KEY="sk-..." # local
# Per-user overrides (0.5.5+)
export DEFAULT_USER_ALICE_OPENAI_API_KEY="sk-..." # shared override for user alice
export DEFAULT_SUPPORT_BOT_USER_ALICE_RESPONSE_PROVIDER_API_KEY="sk-..." # local override for user alice

As of runtime 0.5.4, the authenticated user_id is propagated through secret resolution. When a caller passes user_id on an inference or channel webhook request, the resolver can look up user-scoped local and shared secrets before falling back to the assistant or agentspace scope defaults. This lets multi-tenant deployments store per-user credentials without changing agent specs.

Terminal window
# Production (Kubernetes auth)
export ALQUIMIA_REGISTRY_SECRET_RESOLVER=vault
export VAULT_ADDR=https://vault.internal:8200
export VAULT_MOUNT_POINT=secret
export VAULT_AUTH_METHOD=kubernetes
export VAULT_K8S_ROLE=alquimia-runtime-master
export VAULT_AUTH_ROLE=alquimia-runtime-master
export VAULT_K8S_TOKEN_PATH=/var/run/secrets/vault/service-account/vault-token
# Local development
export ALQUIMIA_REGISTRY_SECRET_RESOLVER=env
export DEFAULT_SUPPORT_BOT_RESPONSE_PROVIDER_API_KEY="sk-..."

A good Vault policy for Alquimia Platform follows these principles:

  • Scope by role — masters need broader registry access than workers.
  • Scope by agentspace — workers serving one agentspace should not read secrets from another.
  • Use dynamic credentials — prefer Vault database secrets engines for PostgreSQL and Redis over static passwords.
  • Avoid broad wildcards — grant access only to the paths each service actually needs.
  • Log everything — enable audit devices so every read and write is recorded.

At minimum, separate the runtime into two service roles:

RoleTypical access
MasterRead/write registry and agentspace secrets, manage identity provisioning, lease its own database credentials.
WorkerRead global secrets and the agentspace it serves, lease its own database credentials, no registry write access.

When agentspace isolation is required, generate a dedicated worker policy for each agentspace or use SPIFFE-based JWT roles created by the agentspace provisioner.

When AGENTSPACE_PROVISIONING_ENABLED=true, the runtime master creates a scoped Vault ACL policy and three JWT auth roles per agentspace:

policy: as-{agentspace_id}-agents
roles: as-{agentspace_id}-reader
as-{agentspace_id}-editor
as-{agentspace_id}-operator

Each role is bound to a SPIFFE ID such as spiffe://alquimia.ai/agentspace/{agentspace_id}/tier/{reader|editor|operator}. Agents authenticate to Vault with their JWT-SVID and receive a token scoped to their agentspace and role tier. This is the recommended way to enforce least-privilege secret access for agent identities.

Configure the Vault JWT auth mount path with AGENTSPACE_PROVISIONING_VAULT_JWT_AUTH_PATH (default jwt). See Runtime configuration and Authorization policies for the full Zero Trust flow.

Vault database secrets engines can issue short-lived PostgreSQL and Redis credentials. The runtime leases these credentials at startup, renews them while in use, and revokes them on shutdown. This reduces the value of stolen credentials and removes the need for manual rotation.

When using dynamic credentials:

  • Create a least-privilege database role for the runtime.
  • Set TTLs that match your compliance requirements without causing excessive churn.
  • Ensure the database user created by Vault can be dropped cleanly when the lease expires.

Some credentials, such as provider API keys and S3 access keys, do not support dynamic issuance. Store these as KV v2 secrets and rotate them manually or through an automated pipeline. Keep the secret reference name stable so agent specs do not need to change during rotation.

Before the runtime starts in production:

  1. Enable the required secrets engines: KV v2, database, and any others you need.
  2. Enable the Kubernetes and JWT auth backends.
  3. Write a master policy scoped to registry, identity provisioning, and its own credentials.
  4. Write worker policies scoped to the agentspaces they serve.
  5. Configure Kubernetes auth roles bound to the runtime service account.
  6. Configure the JWT auth backend with the SPIFFE trust-domain keys.
  7. Populate KV secrets for static credentials.
  8. Set the runtime environment to use Kubernetes auth (VAULT_AUTH_METHOD=kubernetes) and the correct Vault role in both VAULT_K8S_ROLE and VAULT_AUTH_ROLE.