Reference · 11

Managed keys.

Declare a key in code; an operator provides it. Private key bytes never reach your code.

Code declares the keys it needs; an operator creates the key and binds it. Private bytes never reach TypeScript. An operation works only if both the declaration and the binding allow it.

Declare a key

APIContract
key(name: string, options: KeyOptions): ManagedKeyDeclare a key handle and list it in define({ keys }) or a component’s keys; jwtBearer adds its key for you. The same name declared twice must have the same options. Usages must be nonempty and distinct. Don't use a handle during module setup.
ManagedKeyAlgorithm"Ed25519" | "P256" | "RSA" | "HS256" | "A256GCM" | "XSalsa20Poly1305" | "X25519".
KeyUsage"sign" | "verify" | "encrypt" | "decrypt" | "derive" | "publicKey". Each usage must fit the algorithm.
KeyOptions{readonly algorithm: ManagedKeyAlgorithm; readonly usages: readonly KeyUsage[]}. No other properties.
ManagedKeyKeyOptions & {readonly kind: "key"; readonly name: string}. Public metadata only; holding it grants nothing.
publicKey(key: ManagedKey): Uint8ArrayNeeds the publicKey usage. Ed25519/X25519 return 32 raw bytes; P256/RSA return SPKI DER. Symmetric keys have none.
SharedKeyOpaque handle from managed nacl.box.before. Valid only inside that callback; cannot be serialized or forged.
ManagedJWTSignOptions{algorithm?: JWTAlgorithm; typ?: string}. The algorithm follows the key (Ed25519→EdDSA, P256→ES256, RSA→RS256, HS256→HS256); a supplied one must match. Flower sets kid.
ManagedJWTVerifyOptionsJWTValidationOptions & {algorithms?: readonly JWTAlgorithm[]}. If given, algorithms must be just the key's algorithm. Expiry is still required by default.

What each algorithm does

APIContract
Ed25519JWT sign/verify; NaCl sign, sign.open, sign.detached, sign.detached.verify.
P256 / RSA / HS256JWT sign/verify. P256/RSA also allow publicKey. For public-only verification keys, use the raw byte API.
A256GCMJWT encrypt/decrypt. Pass a unique 12-byte nonce, or omit it inside a mutation.
XSalsa20Poly1305NaCl secretbox/open and box.after/open.after. Pass a unique 24-byte nonce on every encryption.
X25519NaCl box/open with a raw peer public key. box.before needs derive and returns a SharedKey. scalarMult.base needs publicKey.
SharedKeyWorks with secretbox/open and box.after/open.after in the same callback. Managed scalarMult and keyPair.fromSecretKey are not supported.
  • The raw byte-key APIs still work alongside managed keys.
  • Managed NaCl uses the active version and stores no version in its output. Save keyVersion(key).version with the ciphertext or signature, or you can't decrypt or verify after a rotation (see key versions).
  • Managed JWTs carry their version in kid. Tokens without a managed kid need the raw public-key API.

Create and bind keys

Use new FlowerAdmin(url, { adminToken }), and admin.partition(id) for a named database. Every key method needs the admin token.

  • Changing methods take ControlOptions (requestId?, signal?). If a response is lost, retry with the same request ID and the same arguments.
  • They return {revision, value, duplicate}, where value is the public catalog.
APIContract
admin.keyList(options?)Return the current public catalog.
admin.keyCacheStats(options?)Cache statistics for the node that received the request, not per database.
admin.keyGenerate(name, algorithm, options?: KeyGenerateOptions)Generate a new key as version 1. The name must be new. Returns metadata only.
admin.keyImport(name, algorithm, sealed, options?: ControlOptions)Import a SealedKeyImport made by flower key seal. Plaintext uploads are rejected.
admin.keyBind(alias, keyName, usages, options?: ControlOptions)Bind a declared alias to a key with the given usages (creates or replaces). Deployed code can never get more than this grants.
admin.keyUnbind(alias, options?: ControlOptions)Remove a binding. Keeps the key. Fails for an unknown alias.
admin.keyRotate(keyName, options?: KeyGenerateOptions)Add a new version and use it for new operations. Old versions stay usable for reading.
admin.keyRevoke(keyName, options?: KeyRevokeOptions)Permanently revoke one version, or all versions if none is given. Revoking the active version blocks new operations until you rotate. Does not delete data.
KeyGenerateOptionsControlOptions & {bits?: number}. RSA only: 2048 (default), 3072, 4096 or 8192. RSA generation can be slow and blocks writes to that database meanwhile; if it times out, retry with the same request ID.
KeyRevokeOptionsControlOptions & {version?: number}. The version must exist.
SealedKeyImport{version: 1; wrappingId: string; nonce: string; ciphertext: string}, base64url. Encrypted; contains no private bytes.
ManagedKeyCatalog{domain: string | null; revision: number; keys; bindings}. keys maps names to {id, algorithm, activeVersion, versions: {version, revoked, retired, destroyed, wrappingId: string | null, kid}[]}; bindings maps aliases to {key, usages}. Empty catalog: null domain, revision 0. Never includes key material.
KeyCacheStats{entries, bytes, budgetBytes, hits, misses, loads, evictions, flightEntries, flightBytes, coalesced}, all numbers, for one node. Reset on restart. bytes is not process memory.
Mount once; grant explicitly
# Install the same protected wrapping file on every authorized server.
umask 077
openssl rand 32 > /secure/flower-wrapping.key
# Start each server with FLOWER_KEYRING_FILE=/secure/flower-wrapping.key.

# SDK CLI: supply FLOWER_URL and FLOWER_ADMIN_TOKEN for your cluster.
flower key generate session-signing --algorithm Ed25519 --request-id create-session-key
flower key bind sessions session-signing --usages sign,verify --request-id bind-sessions
flower deploy sessions.ts --request-id deploy-sessions
flower key rotate session-signing --request-id rotate-sessions-2
flower key list
flower key cache
Import existing material without a plaintext HTTP upload
# Native runtime executable: plaintext stays on this local machine.
/path/to/native/flower key seal --wrapping-key-file /secure/flower-wrapping.key \
  --format pem < private.pem > sealed.json
# SDK CLI sends only the authenticated encrypted envelope.
flower key import imported-signing sealed.json --algorithm Ed25519 \
  --request-id import-signing
  • There are two flower executables. The SDK CLI does network administration. Only the native runtime executable has key seal, which reads plaintext from stdin (--format raw|pem|der, --wrapping-key-file PATH) and writes sealed JSON. flower key import NAME FILE reads that JSON; FILE - means stdin.
  • Keep the wrapping file out of the Raft data directory and its backups. If you lose it, you lose the keys.
  • Import formats: HS256 raw bytes (32 or more); A256GCM, XSalsa20Poly1305, X25519 exactly 32 raw bytes; Ed25519 32-byte seed, 64-byte NaCl secret or PKCS#8; P256 32-byte scalar or PKCS#8; RSA PKCS#8 or PKCS#1 (2048–8192 bits). Public-only keys, certificates, SEC1 and password-protected PEM are not supported.
  • If you pass your own nonces, you must keep them unique across retries and concurrent calls.

Rotation, revocation and reads

  • Each logical database has its own catalog. A named partition keeps its keys when moved.
  • A callback sees one version of the key policy for its whole run; a rotation never switches keys mid-call.
  • When any managed key is declared, all queries and watches read fresh quorum snapshots, even ones marked replica-local. This trades stale-read availability for up-to-date revocation.
  • Rebinding, rotation and revocation recompute dependent derived values. A derived value that depends on a revoked key becomes an error, not an old answer.
  • Rotation keeps old versions; revocation marks them unusable but keeps them. Nothing is destroyed or rewrapped automatically.
  • Revocation only affects new operations. It can't take back bytes already returned or tokens already accepted elsewhere.
  • Keys don't authenticate users. Your methods still decide who may sign, decrypt or read public keys.

Server settings

APIContract
FLOWER_KEYRING_FILEPath to a 32-byte wrapping key, chmod 600 on Unix. Read once at startup. Unset: managed keys are locked. Invalid: the server won't start.
FLOWER_KEY_CACHE_BYTESDefault 16 MiB. Per-node cache of prepared keys. 0 disables it.
FLOWER_KEY_CACHE_TTL_MSDefault 0 (no expiry). A positive value limits how long a prepared key is reused. Not a revocation delay.
  • A node without the right wrapping key refuses managed-key work. Queries that don't touch keys still run.
  • Give every node that may own a database the same wrapping key. Otherwise a partition move pauses until you fix the configuration and restart; then it continues.
  • Backups hold only encrypted key material, but a running server holds unwrapped keys in memory. Use TLS or a trusted network; the default h2c mode is unencrypted.
  • Not supported: KMS/HSM, private-key export, moving between different wrapping keys without a rewrap.

Details: managed-key operating contract.

Key versions

keyVersion(key, version?) returns a ManagedKeyVersion: {kind:"keyVersion",key:ManagedKey,version:string}. Without version it gives the current flower.<keyId>.<version> ID. Store that ID next to NaCl ciphertext and pass it back to decrypt later. Old versions can verify, decrypt and export public keys, but not sign, encrypt or derive.

admin.keyRetire(name, options?: KeyRevokeOptions)Stop new signing/encryption with one or all versions; verification and decryption still work. Rotate to get a new writable version.
admin.keyDestroy(name, options?: KeyRevokeOptions)Delete the stored key material, keeping a tombstone. Doesn't erase backups. Rotating a destroyed RSA key needs explicit bits.
admin.keyRewrap(name, options?: KeyRevokeOptions)Re-encrypt versions under the current wrapping key. To change wrapping keys: list old ones in FLOWER_KEYRING_PREVIOUS_FILES (JSON array of paths), restart, rewrap every catalog, remove the old ones, restart again.

CLI: flower key retire|destroy|rewrap NAME [--version N]. Keep the same --request-id when retrying.