Break-glass recovery
client.recovery, the Recovery namespace, recovers a key domain that has no working domain owner left. An Owner uses it after an offboarding, a lost device or a re-founding of the whole tenant. It uses the same custodian quorum that protects the backup key. Nobody can recover a key domain alone, Seald Healthcare included.
The calls
The samples use the allowed helper from Access decisions.
// the caller holds the Owner role and has a fresh multi-factor sign-inconst ceremony = await allowed( await client.recovery.ask({ domains: [root], newOwner: alice, reason: 'owner-offboarded', reference: 'TICKET-8812' }),);// { ceremonyId, kind, state, askedBy, reason, reference, domains, newOwner, custodians, quorum, openedAt, lapsesAt }
const open = await client.recovery.ceremonies();await client.recovery.cancel(ceremony!.ceremonyId);
// a custodianclient.on('ceremony', async ({ ceremonyId }) => { render(await client.recovery.ceremonies()); // shows who asked, why, which key domains and the new domain owner if (!(await askPerson('Take part?'))) return client.recovery.refuse(ceremonyId); const part = await client.recovery.takePart(ceremonyId, { code: await askForCode() }); if (part.role === 'rebuilt') tell(`Recovered: ${part.domains.length} domains rewrapped to the new owner`); else tell('Share sent; waiting on the rest of the quorum');});let ceremony = try await allowed(client.recovery.ask(domains: .locators([root]), newOwner: alice, reason: "owner-offboarded", reference: "TICKET-8812"))
let open = try await client.recovery.ceremonies()try await client.recovery.cancel(ceremony!.ceremonyId)
for await event in client.on(.ceremony) { render(try await client.recovery.ceremonies()) guard await askPerson("Take part?") else { try await client.recovery.refuse(event.ceremonyId); continue } let part = try await client.recovery.takePart(event.ceremonyId, share: .code(await askForCode())) switch part { case .rebuilt(let domains): tell("Recovered: \(domains.count) domains rewrapped to the new owner") case .wrapped: tell("Share sent; waiting on the rest of the quorum") }}val ceremony = allowed(client.recovery.ask(domains = DomainScope.Locators(listOf(root)), newOwner = alice, reason = "owner-offboarded", reference = "TICKET-8812"))
val open = client.recovery.ceremonies()client.recovery.cancel(ceremony!!.ceremonyId)
client.on<ClientEvent.Ceremony>().collect { event -> render(client.recovery.ceremonies()) if (!askPerson("Take part?")) { client.recovery.refuse(event.ceremonyId); return@collect } val part = client.recovery.takePart(event.ceremonyId, share = ShareInput.Code(askForCode())) when (part) { is Participation.Rebuilt -> tell("Recovered: ${part.domains.size} domains rewrapped to the new owner") is Participation.Wrapped -> tell("Share sent; waiting on the rest of the quorum") }}A Ceremony carries:
kind:recovery, asked withrecovery.ask, orcustodian-replacement, started from backup-key.replaceCustodianstate:open, thenquorumonce enough custodians agree, thencompleted. It can also end ascanceledorlapsed.askedBy: the Owner who askednewOwner: the person who gets access to the recovered key domainsopenedAtandlapsesAt: the window custodians have to respondcustodians: each with its own state,waiting,agreedorrefused
Participation tells a custodian’s device what it just did:
wrapped: the device passed its share to the others and discarded its own copy.rebuilt: this device was the last of the quorum. It rebuilt the key itself, and the call returns the recovereddomains.
What the SDK does for you
- Records the ceremony in the Seald Healthcare Cloud and in Seald Healthcare’s own records as soon as you call
ask, before it contacts any custodian. - Holds a custodian’s share in memory, never on disk, until the token that completes the quorum arrives.
takePartfor the last custodian in the quorum returns only once the recovery finishes. - In that last call, gives
newOwneraccess to every recovered key domain. Then discards every share it touched. - Raises the
ceremonyevent on every Owner and custodian’s device. - Lists a leaver’s unrecovered key domains as
ownerless-domainitems in the worklist until a ceremony recovers them.
Decisions and errors you may see
Outcome or ErrorCode | When | What to do |
|---|---|---|
deny on ask | The caller does not hold the Owner role. | Ask another Owner to start the ceremony. |
lapsed (a Ceremony.state) | Not enough custodians agreed before lapsesAt. | Start a new ceremony. |
A ceremony stuck at open | A custodian has still not responded. | Check custodians for who is still waiting. |
canceled | An Owner canceled before the quorum completed. | Start a new ceremony if the key domains still need recovery. |