Skip to main content

Config Bundle Format

The format of configuration documents, tenant config folders and export bundles, as read by the startup import, the bundle API and the CLI. For the workflow (validate, export, plan, import), see Configuration as code.

Document envelope​

Every portable resource is one JSON document:

{
"$schema": "https://eudiplo.dev/schemas/v3/PresentationConfigFile.schema.json",
"metadata": { "generation": 3, "ownership": "file-managed" },
"spec": { "id": "age-check" }
}
FieldRequiredMeaning
$schemayesResource type and format version. Only the canonical URLs below are accepted; they are resolved from schemas bundled with the backend and CLI, never fetched.
metadata.generationnoInteger ≥ 1. An import whose generation is lower than the stored one is blocked (STALE_GENERATION).
metadata.ownershipnounmanaged or file-managed. Reported on export; ignored on import, which always records file-managed.
specyesThe desired configuration, without runtime state. The ID is spec.id, for clients spec.clientId.

Tenant, KMS, registrar and issuance settings exist once per tenant and have no ID. In tenant config folders, the importer also accepts bare files (only the spec content) at the known paths and wraps them; a document with an unknown $schema or a newer version than the backend supports is rejected.

Resource types and versions​

The URL is https://eudiplo.dev/schemas/v<version>/<File>.schema.json. Older versions are migrated on import and by eudiplo config upgrade; a backend rejects versions newer than its own.

Kind<File>Current versionSingleton
TenantTenantConfigFile1yes
ClientClientConfigFile1
KmsConfigKmsConfigFile1yes
KeyChainKeyChainConfigFile1
RegistrarConfigRegistrarConfigFile1yes
IssuanceConfigIssuanceConfigFile2yes
CredentialConfigCredentialConfigFile1
PresentationConfigPresentationConfigFile3
AttributeProviderAttributeProviderConfigFile1
WebhookEndpointWebhookEndpointConfigFile1
TrustListTrustListConfigFile1
StatusListStatusListConfigFile1

Versions 2 and 3 were introduced with EUDIPLO 9.0; 8.x cannot read them:

  • IssuanceConfig v2 adds the optional offerLifetimeSeconds. A v1 document is migrated unchanged.
  • PresentationConfig v2 replaces registration_cert.body.provided_attestations with the registrar's provides_attestations (a list of credential type identifiers). The migration removes provided_attestations with the warning PROVIDED_ATTESTATIONS_REMOVED; set provides_attestations yourself.
  • PresentationConfig v3 accepts integer and boolean DCQL claim values besides strings and requires them to be non-empty. A v2 document is migrated unchanged; an empty values array stops the migration with a validation error.

Tenant folder layout​

Startup import reads CONFIG_FOLDER (/app/config/config in the image). Every directory in it, including symlinked ones, is a tenant folder; a tenant that does not exist yet is created only if its folder contains info.json.

<CONFIG_FOLDER>/
├── kms.json global KMS providers
└── <tenant-id>/
├── info.json Tenant (name, description, session and status-list defaults)
├── kms.json tenant KMS providers, merged over the global file
├── registrar.json
├── clients/<id>.json
├── key-chains/<id>.json
├── attribute-providers/<id>.json
├── webhook-endpoints/<id>.json
├── trust-lists/<id>.json
├── issuance/
│ ├── issuance.json IssuanceConfig
│ ├── credentials/<id>.json
│ └── status-lists/<id>.json
├── presentation/<id>.json
└── images/<file> logos and credential images, referenced by file name

File names are not significant; the ID comes from the document.

Bundle layout​

An export is a ZIP archive or the same content as one JSON object ({ "manifest": …, "documents": [ … ], "assets": [ … ] }, assets base64-encoded). The ZIP uses the folder layout above for one tenant, with two differences: the files sit at the root of the archive, and the issuance settings are stored as issuance/config.json.

manifest.json contains:

FieldContent
formateudiplo.config-bundle
formatVersion2 for new bundles; version 1 bundles are still read
sourceVersion, exportedAt, tenantOrigin of the export
resources[]kind, id, $schema, path, sha256, ownership, generation per document
assets[]path, contentType, sha256 per binary file
requirements[]Values the target must supply: code, resource, path, message, placeholder
warnings[]Migration warnings

Integrity and limits. Backend and CLI verify that resource identities, paths and SHA-256 checksums match the manifest. Duplicate resources, unsafe paths and ZIP entries not listed in the manifest are rejected. A ZIP may have at most 50 MiB compressed, 100 MiB expanded and 10,000 entries.

Secret and key policy​

Exports never contain secret values or database-held private keys:

ValueIn the exportRequirement codeOn import
Retrievable secrets (KMS, registrar, webhook and attribute-provider credentials)${…} placeholderSECRET_REQUIREDSet the environment variable or replace the value
Client secrets (stored as bcrypt hash)${…} placeholderCLIENT_SECRET_REQUIREDSupply a secret, or "secret": "!generate" to get a new one once in generatedSecrets
Private keys of db key chainskeySource: { "type": "required", "publicJwk": … }PRIVATE_KEY_REQUIREDSupply private-jwk, or { "type": "regenerate" } for new key material
Keys in an external KMSkeySource: { "type": "external-reference", provider, externalKeyId, publicJwk }-The target provider must reach the same key; checked by signing a challenge
Sessions, status values, cachesnot exported--

regenerate keeps the key chain ID but creates a new cryptographic identity: certificates and anything bound to the old key must be renewed.

Placeholders. Any string value can be ${VAR} or ${VAR:default} (uppercase letters, digits, _; an empty variable counts as unset). Values are substituted once and not interpreted again. In tenant folders, unresolved placeholders are handled according to CONFIG_VARIABLE_STRICT:

ValueEffect
skip (default), abort, trueError; the import of this tenant fails, other tenants continue
ignore, falseWarning; the placeholder stays as literal text

Import modes and plan actions​

ModeExisting resourceResource missing from the bundle
createskip (warning RESOURCE_EXISTS)kept
upsertupdate or unchangedkept
replaceupdate or unchangeddelete, only if it was imported earlier from the same source
Plan actionMeaning
createThe resource will be created
updateThe resource will change; the plan lists redacted field changes
unchangedAlready matches; only ownership metadata may be updated
skipExists and is left alone (create mode)
deleteWill be removed (replace mode)
blockedCannot be applied, for example STALE_GENERATION, MISSING_RESOURCE_REFERENCE, STATUS_LIST_LAYOUT_IMMUTABLE or a missing secret or key. One blocked item blocks the whole plan

The import pipeline is: decode, verify checksums, migrate to the current versions, validate, check references and external keys, plan, apply in dependency order (for example key chains before issuance configs), record file-managed ownership. Applying needs the planFingerprint of the reviewed plan; a changed bundle, mode or target state fails with CONFIG_PLAN_STALE. Status lists keep their capacity and bit size: import a new list ID for a different layout.

Management API​

All endpoints act on the tenant of the access token and accept tenant:admin or tenants:manage (roles).

EndpointPurpose
GET /api/config-bundles/export?format=zip|jsonExport the tenant (default json)
POST /api/config-bundles/plan?mode=…Plan a JSON bundle
POST /api/config-bundles/plan/archive?mode=…Plan a ZIP bundle (multipart field bundle)
POST /api/config-bundles/import?mode=…&planFingerprint=…Apply a JSON bundle
POST /api/config-bundles/import/archive?mode=…&planFingerprint=…Apply a ZIP bundle; replace also needs confirmReplace=true
POST /api/config-bundles/documents/upgradeUpgrade one document to the current version
GET /api/config-bundles/operations, GET …/operations/:idLatest 50 operation reports, or one report
POST /api/config-bundles/operations/:id/acknowledge-interruption?confirmWorkerStopped=trueRelease the lock of an interrupted operation
GET /api/config-bundles/resourcesOwnership and generation of every resource
POST /api/config-bundles/resources/:kind/:id/detachMake a file-managed resource unmanaged

Exports, imports and detach actions are recorded in the tenant audit log.