Skip to content

Migration to LightHouse 0.22.X

This page covers the migration to the database-managed trust anchor repository and dynamically configured federation endpoints.

Info

You must be at least on version 0.20.0 to migrate to 0.22.X.

Breaking Changes

Endpoint paths, URLs, authentication trust anchors, and type-specific endpoint configuration (resolve settings, entity collection settings, enroll checker config, etc.) have been removed from the config file.

You MUST migrate these settings to the database.

After migration, all federation endpoint management is done via the Admin API.

Backup Before Migration

Before starting the migration process, create backups of:

  • Your configuration file (config.yaml)
  • Your database

What Changed

Trust Anchor Repository

Previously, trust anchors were configured independently at multiple locations in the config file: endpoints.auth.trust_anchors, per-endpoint auth_trust_anchors, entity checker trust_anchors, and allowed_trust_anchors lists. This meant the same trust anchor could appear at several places with duplicated JWKS, and updating a TA's keys required changing every location.

LightHouse now has a common trust anchor repository stored in the database. All trust anchors and their JWKS are managed in a single place. Every usage (client auth, entity checks, allowed trust anchors) references a trust anchor by entity ID and resolves its JWKS live from the repository at request time.

See Trust Anchor Repository for details.

Automatic JWKS Refreshing

Trust anchors in the repository can optionally have their JWKS automatically refreshed. When enable_jwks_update is set to true for a trust anchor, LightHouse periodically polls the trust anchor's entity configuration and updates the stored JWKS when keys change.

Dynamic Federation Endpoints

Federation endpoints (fetch, list, resolve, trust mark, etc.) are now managed in the database. Their paths, URLs, authentication settings, and type-specific configuration are stored in the federation_endpoints table and can be changed at runtime via the Admin API without restarting LightHouse.

Endpoint paths are served via a single catch-all dispatcher that looks up the path in an in-memory registry. Changes via the Admin API trigger an immediate registry reload and entity configuration cache invalidation.

See Endpoints for details.

Config File Simplification

The endpoints section in the config file has been reduced to only JTI storage settings (auth.jti_backend and auth.jti_cleanup_interval). All other endpoint configuration is now database-managed.

Key Rotation

Key rotation has been managed in the database and configurable via the Admin API (GET/PUT/PATCH /api/v1/admin/keys/rotation) since 0.20.0. The existing options (enabled, interval, overlap) are unchanged.

Two new options have been added to control how far in advance a new key is published in the JWKS before it becomes the active signing key:

Option Description
key_announcement_lead_time Fixed duration (in seconds) how far in advance a new key is published in the JWKS before it becomes the active signing key.
key_announcement_lead_time_ec_multiplier Multiplier for the entity configuration lifetime to compute the announcement lead time. Takes precedence over key_announcement_lead_time if set.

The key announcement lead time gives clients time to fetch the updated entity configuration and cache the new key before the old key is retired. The effective lead time is resolved as follows:

  1. If key_announcement_lead_time_ec_multiplier > 0: multiplier × entity configuration lifetime.
  2. If key_announcement_lead_time > 0: the fixed duration.
  3. Default: max(5 × entity configuration lifetime, 24h).

The result is clamped to a minimum of the entity configuration lifetime; if the configured value is shorter, the EC lifetime is used instead and a warning is logged.

Info

In the past the key announcement lead time was set to one entity configuration lifetime. This is enough only if all client always fetch the entity configuration as soon as it expires. This is obvisouly a wrong assumption and the lead time should be set to a bigger value.

Prefork Mode Removed

The server.prefork config option and LH_SERVER_PREFORK environment variable have been removed. LightHouse now always runs as a single process. If your config file contains prefork: true, it will be silently ignored — remove it from your config.

Prefork mode caused consistency issues with in-memory caches, the trust anchor repository, endpoint registry, and entity configuration metadata, since child processes could not receive updates made via the Admin API in the parent process.

Migration Steps

Step 1: Migrate trust anchors and endpoints to the database

The config2db command connects to whatever database LightHouse uses and migrates the trust anchors and federation endpoints from the config file into it. Use the driver-specific flags below for your database backend (SQLite, MySQL, or PostgreSQL).

./lhmigrate config2db \
  --config=/etc/lighthouse/config.yaml \
  --db-type=sqlite \
  --db-dir=/var/lib/lighthouse \
  --only=trust_anchors,endpoints \
  --update-config
./lhmigrate config2db \
  --config=/etc/lighthouse/config.yaml \
  --db-type=mysql \
  --db-dsn='user:pass@tcp(127.0.0.1:3306)/lighthouse?charset=utf8mb4&parseTime=True' \
  --only=trust_anchors,endpoints \
  --update-config
./lhmigrate config2db \
  --config=/etc/lighthouse/config.yaml \
  --db-type=postgres \
  --db-dsn='host=localhost user=lighthouse password=secret dbname=lighthouse port=5432' \
  --only=trust_anchors,endpoints \
  --update-config

For SQLite, --db-dir points at the directory (or database file). For MySQL and PostgreSQL, --db-dsn is the connection string; --db-dir is not used.

Using Docker

The lhmigrate tool is included in the Docker image. To run it, override the image entrypoint with --entrypoint /lhmigrate, and mount your config file and data directory. For a containerized MySQL or PostgreSQL, also attach to the database container's network so the DSN's hostname resolves:

docker run --rm \
  -v /etc/lighthouse:/config:ro \
  -v /var/lib/lighthouse:/data \
  --entrypoint /lhmigrate \
  oidfed/lighthouse:0.22 \
  config2db \
  --config=/config/config.yaml \
  --db-type=sqlite \
  --db-dir=/data \
  --only=trust_anchors,endpoints \
  --update-config
docker run --rm \
  -v /etc/lighthouse:/config:ro \
  --network db-net \
  --entrypoint /lhmigrate \
  oidfed/lighthouse:0.22 \
  config2db \
  --config=/config/config.yaml \
  --db-type=mysql \
  --db-dsn='user:pass@tcp(mysql:3306)/lighthouse?charset=utf8mb4&parseTime=True' \
  --only=trust_anchors,endpoints \
  --update-config
docker run --rm \
  -v /etc/lighthouse:/config:ro \
  --network db-net \
  --entrypoint /lhmigrate \
  oidfed/lighthouse:0.22 \
  config2db \
  --config=/config/config.yaml \
  --db-type=postgres \
  --db-dsn='host=postgres user=lighthouse password=secret dbname=lighthouse port=5432' \
  --only=trust_anchors,endpoints \
  --update-config

This command:

  1. Collects all trust anchors from every config file location (federation_data.trust_anchors, endpoints.auth.trust_anchors, per-endpoint auth_trust_anchors, entity checker trust_anchors, allowed_trust_anchors) and stores them in the trust_anchors table, deduplicated by entity ID.
  2. Creates federation_endpoints rows for each configured endpoint, storing the path, URL, auth settings (linked to trust anchors via a join table), and type-specific configuration as a JSON blob.
  3. Removes the migrated options from the config file (--update-config).

Dry Run

Add --dry-run -v to preview what would be migrated without making changes. The driver-specific flags work the same as in the examples above:

./lhmigrate config2db \
  --config=/etc/lighthouse/config.yaml \
  --db-type=sqlite \
  --db-dir=/var/lib/lighthouse \
  --only=trust_anchors,endpoints \
  --dry-run -v
./lhmigrate config2db \
  --config=/etc/lighthouse/config.yaml \
  --db-type=mysql \
  --db-dsn='user:pass@tcp(127.0.0.1:3306)/lighthouse?charset=utf8mb4&parseTime=True' \
  --only=trust_anchors,endpoints \
  --dry-run -v
./lhmigrate config2db \
  --config=/etc/lighthouse/config.yaml \
  --db-type=postgres \
  --db-dsn='host=localhost user=lighthouse password=secret dbname=lighthouse port=5432' \
  --only=trust_anchors,endpoints \
  --dry-run -v

Interactive Alternative

lhsetup provides an interactive wizard that covers all DB-managed configuration. It can prepopulate prompts from an existing config file (--config) and is useful both as an alternative to config2db and for configuring options that have no config-file equivalent.

Step 2: Verify the migration

Start LightHouse and verify via the Admin API:

# List all trust anchors
curl -u admin:password https://localhost:8081/api/v1/admin/trust-anchors

# List all federation endpoints
curl -u admin:password https://localhost:8081/api/v1/admin/federation-endpoints

# Check the entity configuration reflects the correct endpoint URLs
curl https://localhost:8080/.well-known/openid-federation

Step 3: (Optional) Enable JWKS refreshing

After migration, you can enable automatic JWKS refreshing for individual trust anchors via the Admin API:

curl -X PUT -u admin:password \
  -H "Content-Type: application/json" \
  -d '{"entity_id":"https://ta.example.com","enable_jwks_update":true,"key_poll_interval":3600}' \
  https://localhost:8081/api/v1/admin/trust-anchors/https://ta.example.com

What Gets Migrated

Trust Anchors

Config Location DB Table Notes
federation_data.trust_anchors trust_anchors Full TA config with JWKS, enable_jwks_update, key_poll_interval
endpoints.auth.trust_anchors trust_anchors Merged with the above, deduplicated by entity ID
Per-endpoint auth_trust_anchors trust_anchors Entity IDs only (no JWKS in config); stored as TA rows if not already present
Entity checker trust_anchors trust_anchors Entity IDs extracted from checker config, stored as TA rows
resolve.allowed_trust_anchors trust_anchors Entity IDs only
entity_collection.allowed_trust_anchors trust_anchors Entity IDs only

Federation Endpoints

Config Location DB Table Notes
endpoints.fetch.path/url/auth_* federation_endpoints Type fetch
endpoints.list.path/url/auth_* federation_endpoints Type list
endpoints.resolve.* federation_endpoints Type resolve; config JSON includes allowed_trust_anchors, use_entity_collection_allowed_trust_anchors, grace_period, time_elapsed_grace_factor, proactive_resolver
endpoints.trust_mark.path/url/auth_* federation_endpoints Type trust_mark
endpoints.trust_mark_status.path/url/auth_* federation_endpoints Type trust_mark_status
endpoints.trust_mark_list.path/url/auth_* federation_endpoints Type trust_mark_listing
endpoints.historical_keys.path/url/auth_* federation_endpoints Type historical_keys
endpoints.enroll.* federation_endpoints Type enroll; config JSON includes checker config with trust_anchors converted to entity-id references
endpoints.enroll_request.path/url/auth_* federation_endpoints Type enroll_request
endpoints.trust_mark_request.path/url/auth_* federation_endpoints Type trust_mark_request
endpoints.entity_collection.* federation_endpoints Type entity_collection; config JSON includes allowed_trust_anchors, interval_seconds, concurrency_limit, pagination_limit
endpoints.auth.all_require_auth (applied per-endpoint) Each endpoint's auth_enabled is set to true if all_require_auth was true

Auth Trust Anchor Mapping

Each endpoint's auth_trust_anchors are linked to trust anchor rows via the federation_endpoint_auth_trust_anchors join table, which has foreign key constraints with ON DELETE CASCADE.

Config File After Migration

After migration, the endpoints section is removed from the config file. JTI storage settings are now under storage.endpoint_auth:

storage:
  driver: sqlite
  data_dir: /data
  endpoint_auth:
    jti_backend: cache
    jti_cleanup_interval: 1h

All other configuration remains in the config file as before (entity_id, server.*, storage.*, signing.*, api.*, stats.*, logging.*, cache.*).

Environment Variable Change

The JTI environment variables have been renamed:

Old New
LH_ENDPOINTS_AUTH_JTI_BACKEND LH_STORAGE_ENDPOINT_AUTH_JTI_BACKEND
LH_ENDPOINTS_AUTH_JTI_CLEANUP_INTERVAL LH_STORAGE_ENDPOINT_AUTH_JTI_CLEANUP_INTERVAL

The lhmigrate config step handles the YAML move automatically, but you must update any environment variables manually.

Post-Migration Management

After migration, use the Admin API to manage trust anchors and endpoints:

Resource Admin API Endpoint
Trust Anchors GET/POST/PUT/DELETE /api/v1/admin/trust-anchors
Federation Endpoints GET/POST/PUT/DELETE /api/v1/admin/federation-endpoints
Endpoint Auth Trust Anchors PUT /api/v1/admin/federation-endpoints/{type}/auth-trust-anchors

See the Admin API documentation for details.

The interactive lhsetup wizard can also be used to review and change any DB-managed configuration at runtime.

New Endpoint Types (Not Migrated)

Two new federation endpoint types were added in 0.22 and have no equivalent in the old config file, so lhmigrate config2db does not create them. If you want to use subordinate JWKS refreshing, create them manually via the Admin API after migration:

Endpoint Type Purpose
jwks_update_trigger A subordinate POSTs to request LightHouse re-fetches its JWKS.
jwks_update A subordinate POSTs a signed JWK Set (application/jwk-set+jwt) with its new keys directly.
# Create the JWKS Update Trigger endpoint
curl -X POST -u admin:password \
  -H "Content-Type: application/json" \
  -d '{"type":"jwks_update_trigger","path":"/jwks/update-trigger","auth_enabled":false}' \
  https://localhost:8081/api/v1/admin/federation-endpoints

# Create the JWKS Update endpoint
curl -X POST -u admin:password \
  -H "Content-Type: application/json" \
  -d '{"type":"jwks_update","path":"/jwks/update"}' \
  https://localhost:8081/api/v1/admin/federation-endpoints

See Subordinate JWKS Refreshing for full details on these endpoints and the periodic-refresh option.

Logging Changes

The logging library has been switched from logrus to zerolog. This affects the logging configuration section:

Removed config keys

The "smart logger" (logging.internal.smart.*) was dead code (never called from any handler) and has been removed.

Added config keys

  • logging.internal.stderr_format — output format for stderr: console (human-friendly, colored; default) or json (structured JSON).
  • logging.internal.dir_format — output format for log files: json (structured JSON; default) or console (human-friendly, no ANSI codes).

Each target (stderr and file) can use a different format independently. For example, stderr_format: console with dir_format: json gives colored console output on the terminal while writing structured JSON to the log file.

Backwards Compatibility

Existing configs with logging.internal.smart keys will simply ignore those keys (YAML unmarshalling ignores unknown fields). No action is required, but you may remove them for cleanliness.