Skip to main content
Version: Next

Configuration

The configuration is YAML. It is read from three sources, each over the last:

  1. A file: tresor-server -config server.yaml. Optional.
  2. TRESOR_CONFIG: a whole YAML document in one environment variable.
  3. One variable per setting: TRESOR_ and the setting's path in capitals, __ between the levels.

Unknown keys are errors, in a file and in the environment alike.

From the environment​

In a container, on Kubernetes or Container Apps, every setting can be an environment variable:

TRESOR_LISTEN=0.0.0.0:8080
TRESOR_PUBLIC_URL=https://secrets.corp.example
TRESOR_TLS__OFFLOAD=true
TRESOR_STATE__KIND=postgres
TRESOR_STATE__DSN='host=db.corp.example user=tresor dbname=tresor sslmode=verify-full'
TRESOR_STATE__AUTH=entra
TRESOR_KEYS__KIND=azurekeyvault
TRESOR_KEYS__KEY=https://corp-kv.vault.azure.net/keys/tresor-kek
TRESOR_AZURE__IDENTITY=managed
TRESOR_ISSUERS="[{issuer: 'https://login.microsoftonline.com/<tenant>/v2.0', audience: '<api client id>'}]"
TRESOR_POLICY__ADMINS='[role:secrets_admin]'

The rules:

  • Text settings take the value as it is: a URL with #, a DSN, null.
  • Lists and sections are read as YAML, so they fit in one variable (TRESOR_ISSUERS, TRESOR_POLICY__ADMINS).
  • A list is given whole. Its items cannot be set one by one: TRESOR_ISSUERS__0__ISSUER is an error.
  • A section is set by its keys (TRESOR_STATE__KIND); a whole document goes in TRESOR_CONFIG.
  • Typos are errors. A TRESOR_ variable shaped like a setting that names none (TRESOR_STATE_KIND, TRESOR_POLICIES__ADMINS) stops the service. Names are in capitals.
  • No weak typing. A number where a text belongs is an error: audience: 0123 must be quoted.
  • An empty variable is an empty text: it clears what the file set.
  • Other TRESOR_ variables are left alone: the ones a *_env setting names, and tresor's test variables.
  • The log names the variables used, never their values. No configuration error quotes a value.

Secrets are never in the configuration itself. A setting names where a secret is: key_env, password_env, client_secret_env. Such a variable must not be a setting's own name.

Settings​

listen, public_url, tls​

Setting
listenhost:port to listen on.
public_urlThe URL clients reach the service at; the discovery's api. It may have a path.
tls.cert, tls.keyThe certificate to serve HTTPS with - read again when the files change (every 30 s; spec 029): a certificate renewed in place (cert-manager's Secret) is served without a restart. A new pair serves once it checks whole (the key the certificate's, the certificate in date); a bad one leaves the old pair serving, logs an error and makes readiness's tls degraded.
tls.offloadTLS ends at the platform's ingress (Container Apps, an ingress controller).

Plain http is allowed only on a loopback address, unless tls.offload is set. With it:

  • the service listens with plain http on any address;
  • public_url must be https;
  • the port must be reachable only through the ingress.

issuers, policy​

These are the reference server's, unchanged. tresor documents them:

  • issuers: the identity providers - issuer, audience, the public client, the flows, the roles and groups claims, how a service's token is told apart from a person's, and the client the service mints tokens for callers with (exchange).
  • policy: admins (who manages secrets and grants their use) and actors (the servers allowed to act for users, and with which verbs).

An actor is a service: client:<id>, optionally pinned to its issuer; or, in this service (spec 031), a role, role:<name>, with its issuer required and one of issuers - every service whose token from that issuer carries the role acts for users with the rule's verbs: one rule for every node group of a platform, not one per client.

policy:
actors:
- {principal: role:hugr_node, issuer: https://idp.example/realms/lake, verbs: [use]}

Only a service's token counts (a person given the role is no actor), and a rule naming the client wins over a role's. A role is broader than a client id: whoever can give the role in the identity provider - to any of its clients - lets that client act for every user. Give such a role to service accounts only, and keep the verbs to use unless the nodes manage secrets for their users. A role rule is only as strong as the issuer's service rule: name the claim's value (equals) where people's tokens could carry the claim too. A token with roles of two rules gets the first rule's verbs, in the order listed. A grant stops working once its node's token no longer carries the role; revoking by actor is per client.

For Microsoft Entra ID, see tresor's Entra guide.

Three settings of an issuer are this service's own (specs 026, 030):

Setting
ignore_rolesRoles of the roles claim never taken as principals - glob patterns: an identity provider's own, Keycloak's offline_access, uma_authorization, default-roles-*. They grant nothing anyway unless a grant or policy.admins names them; this keeps them out of whoami and the console.
ca_filePEM certificates trusted for the issuer's discovery and keys, besides the system's - a cluster's own CA.
discovery_token_fileA bearer token sent (to the issuer's host, and its keys' host) to read its discovery and keys, read at each request - a Kubernetes API server serves them to authenticated accounts only. https issuers only.

A cluster's own ServiceAccounts then log in with their projected tokens: Kubernetes.

state​

Where the service keeps secrets, grants and delegation grants. See State stores.

Setting
state.kindmemory, sqlite, postgres, sqlserver or kubernetes. Required.
state.pathSQLite: the database's file.
state.dsnPostgreSQL, SQL Server: the server, the database, the user. Never a password.
state.authentra (the service's Azure token), aws (an RDS IAM token; AWS) and gcp (Cloud SQL IAM; GCP) - PostgreSQL only -, vault (a database secrets engine's lease; OpenBao and Vault) or password.
state.password_env, state.password_file, state.password_refauth: password: where the password is - one of them. password_ref is ref+k8s://…, ref+azkv://…, ref+vault://…, ref+aws://…, ref+gcp://… or a named source's, outside every material allowlist (every source of its kind).
state.vaultauth: vault: {mount, role, source, set_role} - the engine's mount and role (<mount>/creds/<role>), a named vault source's connection (empty: vault:), and set_role, the group role every lease's user is granted: required on PostgreSQL, refused on SQL Server.
state.max_open_connsThe connection pool; default 10.
state.macSQL stores: check every row's MAC - who may use what cannot change without the KEK (spec 014). On by default since 0.4 (spec 022); false turns the checks off. See State.
state.namespaceKubernetes: where the resources are. The pod's own by default; required outside a pod.
state.instanceKubernetes: the installation's id, in every MAC. The namespace by default; keep it stable.

keys​

The key-encryption key the material is sealed under. See Encryption. Required for every store but memory.

Setting
keys.kindlocal, azurekeyvault, vault, awskms or gcpkms.
keys.key_env, keys.key_filelocal: a 32-byte key, base64.
keys.keyazurekeyvault: https://<vault>/keys/<name>, with no version; vault: the Transit key's name.
keys.mountvault: the Transit mount; default transit.
keys.key, keys.mac_keyawskms: two KMS keys' ARNs, a symmetric encryption key and an HMAC key. See AWS. gcpkms: a crypto key and a MAC key's version; see GCP.
keys.data_key_max_ageA data key older than this is replaced for new values; default 720h.
keys.cache_ttlHow long an unwrapped data key stays in memory; default 5m.
keys.previousKEKs data keys may still be under, read only, during a move to another KEK: a list of {kind, key_env, key_file, key, mount, mac_key}.

material​

Where references may read. See References.

Setting
material.azkv.allow[{vault: corp-vault, prefixes: [lake-, duckdb-]}]: the vaults, and the secret-name prefixes (none: all).
material.azkv.cache_ttlKeep a value read for this long; default 0 (none), at most 5m.
material.azkv.dns_suffixAnother cloud's Key Vault suffix; default .vault.azure.net.
material.k8s.allow[{namespace: data-team, prefixes: [duckdb-]}]: the namespaces, and the Secret-name prefixes (none: all).
material.vault.allow[{mount: secret, prefixes: [duckdb/]}]: the KV v2 mounts, and the path prefixes (none: all). Needs vault:.
material.vault.cache_ttlKeep a value read for this long; default 0, at most 5m.
material.aws.allow[{prefixes: [duckdb/]}]: Secrets Manager names' prefixes (an entry with none: all). Needs aws.region.
material.aws.cache_ttlKeep a value read for this long; default 0, at most 5m.
material.gcp.allow[{project: corp-data, prefixes: [duckdb-]}]: the projects (their ids, never numbers), and the secret-name prefixes (none: all).
material.gcp.cache_ttlKeep a value read for this long; default 0, at most 5m.
material.sourcesNamed sources (spec 008): [{name, kind: vault | azkv | aws | gcp, vault: {…}, azure: {identity, client_id, tenant_id}, aws: {region, role_arn}, allow, cache_ttl, dns_suffix}]. ref+<name>://. See Named sources.

azure​

The service's own identity on Azure: for Key Vault, and for a database login by Entra token.

Setting
azure.identitymanaged (a managed identity), workload (AKS workload identity: the pod's federated ServiceAccount token) or default (DefaultAzureCredential: the az CLI's login, for development).
azure.client_idmanaged: a user-assigned identity's client id. workload: overrides the webhook's AZURE_CLIENT_ID - that identity needs a federated credential for this ServiceAccount (system:serviceaccount:<ns>:<sa>) and the cluster's issuer.

default is for development. Its chain also takes a client secret from the environment (AZURE_CLIENT_SECRET), which is a static secret. Production runs managed (Container Apps) or workload (AKS).

aws​

The service's own identity on AWS (spec 012): for KMS, Secrets Manager, RDS. See AWS.

Setting
aws.regionThe region the service calls. Required when aws is used.
aws.endpoint_urlAnother endpoint for every AWS API (a VPC endpoint, an emulator).
aws.static_credentialsallow: keys from the environment are admitted (development, tests); refused otherwise.
aws.role_arnA role assumed with the service's identity (in a named source: another account).

gcp​

The service's own identity on GCP (spec 012): Application Default Credentials. See GCP.

Setting
gcp.static_credentialsallow: a service account key or a person's login is admitted (development, tests); refused otherwise.

exchange (per issuer)​

The service's client at the issuer, for tokens minted for callers (mint+exchange://, token_exchange secrets). See Token exchange.

Setting
client_idThe service's client. With key_file, read from ZITADEL's key file when unset.
client_authsecret (default), azure, file, keyvault, key_file, vault, awskms or gcpkms.
client_secret_envsecret: the variable holding the client secret.
assertion_filefile: a token read at each request (a projected ServiceAccount token).
key, key_filekeyvault: a Key Vault key URL. vault: a Transit key, <mount>/<key> (needs vault). key_file: ZITADEL's key file or a PEM key. awskms: an asymmetric KMS key's ARN (needs aws). gcpkms: an asymmetric key's version.
kid, x5tWhat the JWT's header names the key by (kid; x5t for Entra's certificates).
assertion_audienceissuer (default) or token_endpoint (Entra).
granttoken_exchange (default; RFC 8693: Keycloak, ZITADEL) or on_behalf_of (Entra). See Token exchange.
omit_client_idfile only: leave client_id out of the request - Keycloak's federated client authentication refuses a client_id that is not the assertion's sub. client_id then names the client in the console only.

Roles from an object: a roles_claim or groups_claim that is an object gives its keys (ZITADEL's urn:zitadel:iam:org:project:roles). An issuer whose claim points at an object used to get nothing, and now gets the keys: check the claim it names.

vault​

OpenBao or HashiCorp Vault (spec 007): the KEK in Transit (keys.kind: vault), ref+vault, client_auth: vault. See OpenBao and Vault.

Setting
vault.addresshttps://… (http only to this machine).
vault.namespaceA namespace, when one is used.
vault.ca_fileA private CA; the system's roots otherwise.
vault.auth.methodkubernetes, jwt or token_file.
vault.auth.mount, vault.auth.roleThe auth method's mount (default: the method's name), and the role.
vault.auth.jwt_filejwt: the token to log in with. kubernetes: the pod's own by default. The chart's vaultToken sets it.
vault.auth.token_filetoken_file: the token a Vault Agent writes, read again when it changes.
keys.kind: vault, keys.key, keys.mountThe Transit key, and its mount (default transit).

ui​

The management console (spec 010) at /ui/, and its API at /admin/v1: administrators only. See Console.

Setting
ui.enabledtrue (default: an upgrade serves the console with no change of configuration). false: no /ui/, no /admin/v1.
ui.environmentA label for the console's badge (prod, staging); unset, none.
ui.allowed_originsThe hosts that mount the console as a microfrontend (https://platform.example): CORS on /ui/mfe/, /v1 and /admin/v1, for them only.
ui.frame_ancestorsPages that may frame /ui/; none by default.
ui.connect_srcMore origins the sign-in calls, for an IdP whose token or user-info endpoint is on another host than its issuer (Google: https://oauth2.googleapis.com; Entra: https://graph.microsoft.com).

Origins are written as a browser sends them: https://host[:port], no path, no default port, no wildcard.

People sign in with the issuers that have a client_id: register <public_url>/ui/callback as a redirect URI of that client.

cors​

Web pages that run tresor in DuckDB-wasm call the service from the browser, across origins (tresor's protocol, Browsers; spec 022).

Setting
cors.allowed_originsThe pages' origins (https://notebook.example): CORS on discovery (/.well-known/duckdb-secrets) and /v1, for them only.
  • The preflight allows the protocol's methods and the headers Authorization, Content-Type, Accept, Delegation, If-Match, If-None-Match, traceparent; answers expose ETag and X-Request-Id. No credentials: no cookie is sent or honoured, the bearer token is the only one.
  • Separate from ui.allowed_origins (the console's hosts): neither list implies the other.
  • An origin as a browser sends it: https://host[:port] (http only to this machine), no path, no wildcard.
  • The page's sign-in is the IdP's: register the page's origin there too (a SPA client, CORS on its token endpoint).

dynamic​

Generated parameters (spec 023).

Setting
dynamic.login_hostsThe hosts a service login may call: a name, or *.domain. Empty: no login calls out.
dynamic.identitiesThe workload identities and client keys a rule or a jwt_assertion may use: name (once each) and kind, and per kind: azure - client_id, scopes, destinations; gcp - service_account, scopes, destinations; aws_rds - role_arn, endpoints, users, destinations; aws_sts - role_arn (required), duration (15m-12h, default 1h), destinations; jwt - client (as exchange: client_auth keyvault, vault, awskms, gcpkms or key_file), audience, token_urls; snowflake - account (no .), user, public_key_fp, client (client_auth and key or key_file only), token_urls. Another kind's field is refused; every kind but jwt and snowflake lists its destinations.
dynamic.marginexpires_at is this long before what was generated expires, at most half of what is left: up to 1h; unset (or 0), 10m.

audit, telemetry​

See Observability.

Setting
audit.levelall (default), changes (no successful read or inspection; a reveal is kept), or off (nothing).
telemetry.tracestrue (default): spans under tresor's trace. false: the audit's trace_id only.

OpenTelemetry's export uses its standard variables: OTEL_EXPORTER_OTLP_ENDPOINT (nothing is exported without one), OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, OTEL_SDK_DISABLED.