-
-
Notifications
You must be signed in to change notification settings - Fork 291
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement standard keymanager API (#3522)
* Add keymanager backend * Add keymanager server * Add private local signer * Fix comment typo * Update test types * Update package.json and license * Hooking up the keymanager server - still manually registering a dummy api that returns static data * Revert "Hooking up the keymanager server - still manually registering a dummy api that returns static data" This reverts commit c3c220e. * using 9657 as default port for key manager server. Also not overriding defaults with undefined * starting keymanager server also in dev * First stab at implementing deleting keys from persistant storage * updating parsing of slashing data to make POST request work * leaving some todo * switching deleting file to async. Still need to take care of logging in case deleting fails * first dash at persisting keys for later restarts * Adding (local signer for now) and removing of signer in ValidatorStore. * deleting and adding working again with remote signer merged in * fix lint errors * fix lint errors * fix lint errors - finally * temp * Moving keymanager to its own package to make the validator package nodejs agnostic * track test file * removing some outdated TODOs * adding the generic server test for keymanager api * removing an unused function and two empty test files * keeping inline with existing pattern of using _config when config is not used * Added first test for KeymanagerApi#listKeys * Added test for KeymanagerApi#importKeystores * Added test for KeymanagerApi#deleteKeystores * move lock file to utils * making sure keystore are added and deleted in all scenarios * temp making some assertion less strict * adding back matchers in the test * removing e2e test in script since none yet * On delete remove key from internal list of keys known my the keymanager * first shot at adding bearer authentication for keymanager server * Using async pattern again so as to fix test that only fails on ci * log location of keymanager's bearer token on startup * Undoing the addition of lockfile to lodestar-util as it is a package that should be filesystem/nodejs agnostic * importing lockfile in test * Stubbing out the keystore. All tests should now pass on CI * remove duplicated start of validator * When using dev command, make it actually possible to not run a validator with a beacon node * Passing in the directory where the keymanager's bearer token will be stored * Properly setting the path to keystore file to be deleted * adding methods to remove signer from various duties, exposing and calling in keymanager. * Added tests for BlockDutiesService#remove * fix linting errors * Added tests for AttestationDutiesService#remove * Added tests for SyncCommitteeDutiesService#remove * Added test to directly test IndicesService * update test to confirm unremoved pubkey is not affected by the remove call * Added the scaffolding to run e2e test that makes use of the keymanager * shut down node and validators in keymanager e2e test * Making sure by default bearer authentication is used for keymanager api * Adding e2e for keymanager api * adding some eslint ignore comments * Fix linting errors * Processing some todos * Process left TODOs * log a warning if kemanager started without auth * fix compilation error when running sim:singleThread test * import all modules from same package in same line * fix compilation error due to import * Added logger to KeymanagerApi * Fixed compilation error in key managers e2e test due to changes that added logger to KeymanagerApi * removing TODO after deciding to action needed * removing download-spec-tests from keymanager-server script * test request is denied if no auth for keymanager * trying closing keymanager before beacon node * setting port right * Some improvement to the keymanager e2e test * Added signing to e2e test for key manager * Added support for interactions with remote signer validators. Removed some unit tests in place of e2e * optimise imports * optimize some tests assertion * minor correction to capitalisation * register callback to delete locks in same loop that adds to signers * fix compilation error * adding lockfile as dependency following alphabetical order * Improving the registering of shutdown callbacks * moving keymanager options out of beacon node options * having back default value for startValidators flag * Removed unnecessary comment * removed the parse-numbers setting * Switching to making imported keystore path deterministic by pubkey * Using Array constructor with length is more efficient * Move some keystore test utility function to a seperate file * Removed some more suppress comments * Removed unnecessary dependency * using 5062 as default port for keymanager * Improve keymanager and keymanager e2e tests * Removed unnecessary test utility function * Removed the use of mapvalues * Not exposing duties * Minor comment improvement * Have slashing protection be part of validator and use that in the key manager * Remove getSignerLocal * switch use of forEach for for-of * revert changes to local.md * optimise import order * Moved typing info for lockfile to devDependencies * Some renaming * replace join with path.join for clarity * setting derivation path * Introduced a utility for keystore file, that is used both in importing and deleting keys * having duties service classes back to private * re-adding test:unit * switching to using path.join * Introduced a more robust cleanup for lightclient.test.ts * import optimisation * putting all closing of nodes/validators into afterEach call * temp skip keymanager e2e test * switching node port back to 9596. It should not be any problem as long as all tests shuts down node properly * switch test port to 19596 see if this fixes tests * Immediately register clean up for servers * Temp skip a failing test * Using tmp dir for keeping bearer token for test cases where keymanager needs bearer token * move the lockfile to keymanager * Added tests to cover bearer token authentication flow * Setting derivationPath back to empty string. Implementing getting derivationPath would be done later * Fix type error Co-authored-by: Cayman <[email protected]> Co-authored-by: Dadepo Aderemi <[email protected]>
- Loading branch information
1 parent
08f98c2
commit c5997e0
Showing
59 changed files
with
2,154 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./lib/keymanager"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// eslint-disable-next-line @typescript-eslint/no-require-imports | ||
module.exports = require("./lib/keymanager"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./lib/keymanager/server"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// eslint-disable-next-line @typescript-eslint/no-require-imports | ||
module.exports = require("./lib/keymanager/server"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import {IHttpClient, generateGenericJsonClient} from "../client/utils"; | ||
import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "./routes"; | ||
import {IChainForkConfig} from "@chainsafe/lodestar-config"; | ||
|
||
export function getClient(_config: IChainForkConfig, httpClient: IHttpClient): Api { | ||
const reqSerializers = getReqSerializers(); | ||
const returnTypes = getReturnTypes(); | ||
// All routes return JSON, use a client auto-generator | ||
return generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import {IChainForkConfig} from "@chainsafe/lodestar-config"; | ||
import {HttpClient, HttpClientOptions} from "../client"; | ||
import {IHttpClient} from "../client/utils"; | ||
import {Api} from "./routes"; | ||
import * as keymanager from "./client"; | ||
|
||
export {ImportStatus, DeletionStatus, KeystoreStr, SlashingProtectionData, PubkeyHex, Api} from "./routes"; | ||
|
||
/** | ||
* REST HTTP client for all keymanager routes | ||
*/ | ||
export function getClient(config: IChainForkConfig, opts: HttpClientOptions, httpClient?: IHttpClient): Api { | ||
if (!httpClient) httpClient = new HttpClient(opts); | ||
|
||
return keymanager.getClient(config, httpClient); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import {ReturnTypes, RoutesData, Schema, reqEmpty, ReqSerializers, ReqEmpty, jsonType} from "../utils"; | ||
|
||
export enum ImportStatus { | ||
/** Keystore successfully decrypted and imported to keymanager permanent storage */ | ||
imported = "imported", | ||
/** Keystore's pubkey is already known to the keymanager */ | ||
duplicate = "duplicate", | ||
/** Any other status different to the above: decrypting error, I/O errors, etc. */ | ||
error = "error", | ||
} | ||
|
||
export enum DeletionStatus { | ||
/** key was active and removed */ | ||
deleted = "deleted", | ||
/** slashing protection data returned but key was not active */ | ||
not_active = "not_active", | ||
/** key was not found to be removed, and no slashing data can be returned */ | ||
not_found = "not_found", | ||
/** unexpected condition meant the key could not be removed (the key was actually found, but we couldn't stop using it) - this would be a sign that making it active elsewhere would almost certainly cause you headaches / slashing conditions etc. */ | ||
error = "error", | ||
} | ||
|
||
/** | ||
* JSON serialized representation of a single keystore in EIP-2335: BLS12-381 Keystore format. | ||
* ``` | ||
* '{"version":4,"uuid":"9f75a3fa-1e5a-49f9-be3d-f5a19779c6fa","path":"m/12381/3600/0/0/0","pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"8ff8f22ef522a40f99c6ce07fdcfc1db489d54dfbc6ec35613edf5d836fa1407"},"message":""},"checksum":{"function":"sha256","params":{},"message":"9678a69833d2576e3461dd5fa80f6ac73935ae30d69d07659a709b3cd3eddbe3"},"cipher":{"function":"aes-128-ctr","params":{"iv":"31b69f0ac97261e44141b26aa0da693f"},"message":"e8228bafec4fcbaca3b827e586daad381d53339155b034e5eaae676b715ab05e"}}}' | ||
* ``` | ||
*/ | ||
export type KeystoreStr = string; | ||
|
||
/** | ||
* JSON serialized representation of the slash protection data in format defined in EIP-3076: Slashing Protection Interchange Format. | ||
* ``` | ||
* '{"metadata":{"interchange_format_version":"5","genesis_validators_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},"data":[{"pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","signed_blocks":[],"signed_attestations":[]}]}' | ||
* ``` | ||
*/ | ||
export type SlashingProtectionData = string; | ||
|
||
/** | ||
* The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._ | ||
* ``` | ||
* "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" | ||
* ``` | ||
*/ | ||
export type PubkeyHex = string; | ||
|
||
type Statuses<Status> = { | ||
status: Status; | ||
message?: string; | ||
}[]; | ||
|
||
type ImportKeystoresReq = { | ||
keystores: KeystoreStr[]; | ||
passwords: string[]; | ||
slashingProtection: SlashingProtectionData; | ||
}; | ||
|
||
type ListKeysResponse = { | ||
validatingPubkey: PubkeyHex; | ||
/** The derivation path (if present in the imported keystore) */ | ||
derivationPath?: string; | ||
/** The key associated with this pubkey cannot be deleted from the API */ | ||
readonly?: boolean; | ||
}; | ||
|
||
export type Api = { | ||
/** | ||
* List all validating pubkeys known to and decrypted by this keymanager binary | ||
* | ||
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml | ||
*/ | ||
listKeys(): Promise<{ | ||
data: ListKeysResponse[]; | ||
}>; | ||
|
||
/** | ||
* Import keystores generated by the Eth2.0 deposit CLI tooling. `passwords[i]` must unlock `keystores[i]`. | ||
* | ||
* Users SHOULD send slashing_protection data associated with the imported pubkeys. MUST follow the format defined in | ||
* EIP-3076: Slashing Protection Interchange Format. | ||
* | ||
* @param keystores JSON-encoded keystore files generated with the Launchpad | ||
* @param passwords Passwords to unlock imported keystore files. `passwords[i]` must unlock `keystores[i]` | ||
* @param slashingProtection Slashing protection data for some of the keys of `keystores` | ||
* @returns Status result of each `request.keystores` with same length and order of `request.keystores` | ||
* | ||
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml | ||
*/ | ||
importKeystores( | ||
keystoresStr: KeystoreStr[], | ||
passwords: string[], | ||
slashingProtectionStr: SlashingProtectionData | ||
): Promise<{ | ||
data: Statuses<ImportStatus>; | ||
}>; | ||
|
||
/** | ||
* DELETE must delete all keys from `request.pubkeys` that are known to the keymanager and exist in its | ||
* persistent storage. Additionally, DELETE must fetch the slashing protection data for the requested keys from | ||
* persistent storage, which must be retained (and not deleted) after the response has been sent. Therefore in the | ||
* case of two identical delete requests being made, both will have access to slashing protection data. | ||
* | ||
* In a single atomic sequential operation the keymanager must: | ||
* 1. Guarantee that key(s) can not produce any more signature; only then | ||
* 2. Delete key(s) and serialize its associated slashing protection data | ||
* | ||
* DELETE should never return a 404 response, even if all pubkeys from request.pubkeys have no extant keystores | ||
* nor slashing protection data. | ||
* | ||
* Slashing protection data must only be returned for keys from `request.pubkeys` for which a | ||
* `deleted` or `not_active` status is returned. | ||
* | ||
* @param pubkeys List of public keys to delete. | ||
* @returns Deletion status of all keys in `request.pubkeys` in the same order. | ||
* | ||
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml | ||
*/ | ||
deleteKeystores( | ||
pubkeysHex: string[] | ||
): Promise<{ | ||
data: Statuses<DeletionStatus>; | ||
slashingProtection: SlashingProtectionData; | ||
}>; | ||
}; | ||
|
||
export const routesData: RoutesData<Api> = { | ||
listKeys: {url: "/eth/v1/keystores", method: "GET"}, | ||
importKeystores: {url: "/eth/v1/keystores", method: "POST"}, | ||
deleteKeystores: {url: "/eth/v1/keystores", method: "DELETE"}, | ||
}; | ||
|
||
export type ReqTypes = { | ||
listKeys: ReqEmpty; | ||
importKeystores: {body: ImportKeystoresReq}; | ||
deleteKeystores: {body: {pubkeys: string[]}}; | ||
}; | ||
|
||
export function getReqSerializers(): ReqSerializers<Api, ReqTypes> { | ||
return { | ||
listKeys: reqEmpty, | ||
importKeystores: { | ||
writeReq: (keystores, passwords, slashingProtection) => ({body: {keystores, passwords, slashingProtection}}), | ||
parseReq: ({body: {keystores, passwords, slashingProtection}}) => [keystores, passwords, slashingProtection], | ||
schema: {body: Schema.Object}, | ||
}, | ||
deleteKeystores: { | ||
writeReq: (pubkeys) => ({body: {pubkeys}}), | ||
parseReq: ({body: {pubkeys}}) => [pubkeys], | ||
schema: {body: Schema.Object}, | ||
}, | ||
}; | ||
} | ||
|
||
/* eslint-disable @typescript-eslint/naming-convention */ | ||
export function getReturnTypes(): ReturnTypes<Api> { | ||
return { | ||
listKeys: jsonType(), | ||
importKeystores: jsonType(), | ||
deleteKeystores: jsonType(), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {IChainForkConfig} from "@chainsafe/lodestar-config"; | ||
import {ServerRoutes, getGenericJsonServer} from "../server/utils"; | ||
import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "./routes"; | ||
|
||
export function getRoutes(config: IChainForkConfig, api: Api): ServerRoutes<Api, ReqTypes> { | ||
// All routes return JSON, use a server auto-generator | ||
return getGenericJsonServer<Api, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import {config} from "@chainsafe/lodestar-config/default"; | ||
import {Api, DeletionStatus, ImportStatus, ReqTypes} from "../../src/keymanager/routes"; | ||
import {getClient} from "../../src/keymanager/client"; | ||
import {getRoutes} from "../../src/keymanager/server"; | ||
import {runGenericServerTest} from "../utils/genericServerTest"; | ||
|
||
describe("keymanager", () => { | ||
runGenericServerTest<Api, ReqTypes>(config, getClient, getRoutes, { | ||
listKeys: { | ||
args: [], | ||
res: { | ||
data: [ | ||
{ | ||
validatingPubkey: | ||
// randomly pregenerated pubkey | ||
"0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff2ff92376b778798365e488dab07a652eb04576", | ||
derivationPath: "m/12381/3600/0/0/0", | ||
readonly: false, | ||
}, | ||
], | ||
}, | ||
}, | ||
importKeystores: { | ||
args: [["key1"], ["pass1"], "slash_protection"], | ||
res: {data: [{status: ImportStatus.imported}]}, | ||
}, | ||
deleteKeystores: { | ||
args: [["key1"]], | ||
res: {data: [{status: DeletionStatus.deleted}], slashingProtection: "slash_protection"}, | ||
}, | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.