External requests are authenticated before the controller runs.
Supported auth types:
| Type | Header | Use |
|---|---|---|
dev |
X-DEV-TOKEN |
development |
jwt |
X-JWT-TOKEN |
real deployments |
Dev auth needs a separate dev config file. It is loaded from keeper.dev.config.location.
Example dev.conf:
keeper {
dev {
token = "dev-token"
permissions = [
"tkeeper.system.init",
"tkeeper.system.unseal",
"tkeeper.dkg.create",
"tkeeper.dkg.rotate",
"tkeeper.dkg.refresh",
"tkeeper.key.*.public",
"tkeeper.key.*.sign",
"tkeeper.key.*.verify",
"tkeeper.key.*.destroy",
"tkeeper.storage.write",
"tkeeper.compliance.inventory"
]
}
}
JWT auth:
auth {
type = "jwt"
jwt {
jwks-location = "https://issuer.example/.well-known/jwks.json"
issuer = "https://issuer.example"
audience = "tkeeper"
refresh = 15m
clock-skew = 15s
}
}
JWT tokens must contain:
subaudexppermissionsas a string list claim
aud may be a single value or an array. TKeeper checks that it contains auth.jwt.audience.
If auth.jwt.issuer is configured, the token iss claim must match it. Configure issuer in production to bind tokens to the expected identity provider.
exp is required. nbf is optional, but when present TKeeper rejects the token before that time.
auth.jwt.clock-skew defaults to 15s and must not be negative. It is used as a small tolerance for exp and nbf checks to handle clock drift between the issuer and Keeper nodes.
Common permissions:
| Permission | Allows |
|---|---|
tkeeper.system.init |
initialize keeper |
tkeeper.system.unseal |
unseal |
tkeeper.system.seal |
seal |
tkeeper.dkg.create |
create a key |
tkeeper.dkg.rotate |
rotate a key |
tkeeper.dkg.refresh |
refresh shares |
tkeeper.key.{keyId}.public |
read public key |
tkeeper.key.{keyId}.sign |
sign |
tkeeper.key.{keyId}.verify |
verify |
tkeeper.key.{keyId}.encrypt |
ECIES encrypt |
tkeeper.key.{keyId}.decrypt |
ECIES decrypt |
tkeeper.key.{keyId}.destroy |
destroy key |
tkeeper.storage.write |
trusted-dealer import |
tkeeper.consistency.fix |
run consistency fix |
tkeeper.integrity.rotate |
rotate audit integrity key |
tkeeper.audit.log.verify |
verify signed audit log lines |
tkeeper.compliance.inventory |
read asset inventory |
tkeeper.control.system |
read control-plane system state |
tkeeper.control.sinks |
read audit sink state |
tkeeper.expired.view |
view expired key material where supported |
tkeeper.quorum.promote |
promote from mono to threshold |
Wildcards are supported:
tkeeper.key.*.sign
tkeeper.key.prefix-*.sign
tkeeper.key.*.*
Wildcards match dot-separated permission segments. tkeeper.key.*.sign matches tkeeper.key.wallet.sign, but not tkeeper.key.team.wallet.sign.
Negative permissions start with -. They remove access granted by a broader permission:
tkeeper.key.*.sign
-tkeeper.key.hot-wallet.sign
This grants signing on all one-segment key ids except hot-wallet.
Internal Peer Auth
Peer-to-peer calls use TKeeper's internal request signing. This is separate from public API auth.
Each internal request carries:
X-INSTANCE-ID
X-INTENDED-FOR
X-TIMESTAMP
X-NONCE
X-KEY-ID
X-PUBLIC-KEY
X-BOOT-PROOF
X-SIGNATURE
The request is signed by the peer's internal Ed25519 key. The nonce must be unique and the timestamp must be fresh. On first contact, a peer proves its key with the shared bootstrap token. After that, the public key is pinned.
Internal TLS can still be enabled, but it is transport security. Peer authorization is the signed-request layer.
Frequent Problems
UNAUTHENTICATED
The token is missing, invalid, or signed by a key that is not in JWKS.
ACCESS_DENIED
The token is valid, but the permissions claim does not allow this operation.
Check negative permissions too. A matching -permission wins over a broad grant.