Firekeeper is a bridge between Fireblocks Agent and TKeeper.
It accepts signing requests from Fireblocks Agent, validates the shared secret in the Authorization header, and forwards signing operations to TKeeper.
- accepts Fireblocks Agent requests
- authenticates the agent with
fireblocks.secret - forwards signing to TKeeper
- supports two TKeeper auth modes:
DEVOIDC
- supports optional custom trust store for outgoing TLS to TKeeper
- uses PostgreSQL for persistence
- Java 21
- PostgreSQL
- TKeeper reachable from Firekeeper
- Fireblocks Agent configured to call Firekeeper
Firekeeper uses Liquibase migrations from:
src/main/db/changelog/changelog.yaml
Apply these migrations before running the service in any environment where the database is not already initialized.
If your deployment applies Liquibase migrations externally, make sure the full changelog is executed before Firekeeper starts processing requests.
Generate jOOQ sources first:
./gradlew jooqCodegenBuild the application:
./gradlew buildRun it:
java -jar build/quarkus-app/quarkus-run.jarThe project ships with a standard Quarkus JVM image based on UBI9/OpenJDK 21.
Build the jar first:
./gradlew buildThen build the image from the project root:
docker build -f src/main/docker/Dockerfile.jvm -t firekeeper:latest .application.properties example:
# -----------------------------
# Fireblocks Agent authentication
# -----------------------------
fireblocks.secret=change-me
# -----------------------------
# TKeeper connection
# -----------------------------
keeper.url=https://tkeeper.example.com
keeper.auth.type=DEV
keeper.auth.dev.token=change-me
# Optional trust store for TKeeper TLS
# keeper.trust-store.path=/app/truststore.p12
# keeper.trust-store.password=change-me
# -----------------------------
# Signing processor tuning
# -----------------------------
keeper.signature.max-attempts=3
keeper.signature.ecdsa.batch-size=8
keeper.signature.ecdsa.interval-ms=250
keeper.signature.ecdsa.timeout-ms=60000
keeper.signature.eddsa.batch-size=20
keeper.signature.eddsa.interval-ms=75
keeper.signature.eddsa.timeout-ms=10000
# -----------------------------
# PostgreSQL datasource
# -----------------------------
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=firekeeper
quarkus.datasource.password=change-me
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/firekeeper
quarkus.datasource.jdbc.max-size=16
quarkus.datasource.jdbc.min-size=2
# -----------------------------
# Liquibase
# -----------------------------
quarkus.liquibase.migrate-at-start=true
quarkus.liquibase.change-log=src/main/db/changelog/changelog.yaml| Property | Required | Description |
|---|---|---|
fireblocks.secret |
yes | Exact value expected in the incoming Authorization header from Fireblocks Agent. Fireblocks Agent forwards whatever you set in CUSTOMER_SERVER_AUTHORIZATION, so keep the full scheme here if you use one, for example Bearer .... |
| Property | Required | Description |
|---|---|---|
keeper.url |
yes | Public base URL of TKeeper. |
keeper.auth.type |
yes | DEV or OIDC. |
keeper.auth.dev.token |
when DEV |
Token used by DevTokenAuth. |
keeper.trust-store.path |
optional | Trust store path for TLS to TKeeper. |
keeper.trust-store.password |
optional | Trust store password. |
| Property | Default | Description |
|---|---|---|
keeper.signature.max-attempts |
3 |
Maximum retries before the item is marked failed. |
keeper.signature.ecdsa.batch-size |
8 |
Number of ECDSA items claimed from the database per tick. |
keeper.signature.ecdsa.interval-ms |
250 |
Delay between ECDSA scheduler runs. |
keeper.signature.ecdsa.timeout-ms |
60000 |
Processing timeout for ECDSA items before they can be reclaimed. |
keeper.signature.eddsa.batch-size |
20 |
Number of EdDSA items claimed from the database per tick. |
keeper.signature.eddsa.interval-ms |
75 |
Delay between EdDSA scheduler runs. |
keeper.signature.eddsa.timeout-ms |
10000 |
Processing timeout for EdDSA items before they can be reclaimed. |
Firekeeper uses the standard Quarkus JDBC datasource.
Minimum setup:
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=firekeeper
quarkus.datasource.password=change-me
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/firekeeperA practical starting point for the pool:
quarkus.datasource.jdbc.max-size=16
quarkus.datasource.jdbc.min-size=2When keeper.auth.type=OIDC, configure a named OIDC client called keeper.
keeper.auth.type=OIDC
keeper.url=https://tkeeper.example.com
quarkus.oidc-client.keeper.auth-server-url=https://id.example.com/realms/main
quarkus.oidc-client.keeper.client-id=firekeeper
quarkus.oidc-client.keeper.grant.type=client
quarkus.oidc-client.keeper.credentials.secret=change-meIf you want secrets to come from HashiCorp Vault instead of plain properties, configure Vault as a config source.
Example:
quarkus.vault.url=http://localhost:8200
quarkus.vault.authentication.client-token=change-me
quarkus.vault.secret-config-kv-path=myapps/firekeeperThen values such as fireblocks.secret, keeper.auth.dev.token, database credentials, or OIDC secrets can be provided by Vault.
Environment variables still override Vault values.
If you use Sentry for error logging, add the extension and set the DSN.
quarkus.log.sentry=true
quarkus.log.sentry.dsn=https://examplePublicKey@o0.ingest.sentry.io/0
quarkus.log.sentry.minimum-event-level=ERROR