mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 19:51:47 +00:00
Implement standard keystore API (#2736)
## Issue Addressed Implements the standard key manager API from https://ethereum.github.io/keymanager-APIs/, formerly https://github.com/ethereum/beacon-APIs/pull/151 Related to https://github.com/sigp/lighthouse/issues/2557 ## Proposed Changes - [x] Add all of the new endpoints from the standard API: GET, POST and DELETE. - [x] Add a `validators.enabled` column to the slashing protection database to support atomic disable + export. - [x] Add tests for all the common sequential accesses of the API - [x] Add tests for interactions with remote signer validators - [x] Add end-to-end tests for migration of validators from one VC to another - [x] Implement the authentication scheme from the standard (token bearer auth) ## Additional Info The `enabled` column in the validators SQL database is necessary to prevent a race condition when exporting slashing protection data. Without the slashing protection database having a way of knowing that a key has been disabled, a concurrent request to sign a message could insert a new record into the database. The `delete_concurrent_with_signing` test exercises this code path, and was indeed failing before the `enabled` column was added. The validator client authentication has been modified from basic auth to bearer auth, with basic auth preserved for backwards compatibility.
This commit is contained in:
@@ -1,14 +1,18 @@
|
||||
mod api_secret;
|
||||
mod create_validator;
|
||||
mod keystores;
|
||||
mod tests;
|
||||
|
||||
use crate::ValidatorStore;
|
||||
use account_utils::mnemonic_from_phrase;
|
||||
use create_validator::{create_validators_mnemonic, create_validators_web3signer};
|
||||
use eth2::lighthouse_vc::types::{self as api_types, PublicKey, PublicKeyBytes};
|
||||
use eth2::lighthouse_vc::{
|
||||
std_types::AuthResponse,
|
||||
types::{self as api_types, PublicKey, PublicKeyBytes},
|
||||
};
|
||||
use lighthouse_version::version_with_platform;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slog::{crit, info, Logger};
|
||||
use slog::{crit, info, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
@@ -106,7 +110,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
// Configure CORS.
|
||||
let cors_builder = {
|
||||
let builder = warp::cors()
|
||||
.allow_methods(vec!["GET", "POST", "PATCH"])
|
||||
.allow_methods(vec!["GET", "POST", "PATCH", "DELETE"])
|
||||
.allow_headers(vec!["Content-Type", "Authorization"]);
|
||||
|
||||
warp_utils::cors::set_builder_origins(
|
||||
@@ -125,7 +129,20 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
}
|
||||
|
||||
let authorization_header_filter = ctx.api_secret.authorization_header_filter();
|
||||
let api_token_path = ctx.api_secret.api_token_path();
|
||||
let mut api_token_path = ctx.api_secret.api_token_path();
|
||||
|
||||
// Attempt to convert the path to an absolute path, but don't error if it fails.
|
||||
match api_token_path.canonicalize() {
|
||||
Ok(abs_path) => api_token_path = abs_path,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
log,
|
||||
"Error canonicalizing token path";
|
||||
"error" => ?e,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let signer = ctx.api_secret.signer();
|
||||
let signer = warp::any().map(move || signer.clone());
|
||||
|
||||
@@ -154,9 +171,15 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
})
|
||||
});
|
||||
|
||||
let inner_ctx = ctx.clone();
|
||||
let log_filter = warp::any().map(move || inner_ctx.log.clone());
|
||||
|
||||
let inner_spec = Arc::new(ctx.spec.clone());
|
||||
let spec_filter = warp::any().map(move || inner_spec.clone());
|
||||
|
||||
let api_token_path_inner = api_token_path.clone();
|
||||
let api_token_path_filter = warp::any().map(move || api_token_path_inner.clone());
|
||||
|
||||
// GET lighthouse/version
|
||||
let get_node_version = warp::path("lighthouse")
|
||||
.and(warp::path("version"))
|
||||
@@ -348,7 +371,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::path("keystore"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(validator_dir_filter)
|
||||
.and(validator_dir_filter.clone())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and(runtime_filter.clone())
|
||||
@@ -451,9 +474,9 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(validator_store_filter)
|
||||
.and(signer)
|
||||
.and(runtime_filter)
|
||||
.and(validator_store_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and(runtime_filter.clone())
|
||||
.and_then(
|
||||
|validator_pubkey: PublicKey,
|
||||
body: api_types::ValidatorPatchRequest,
|
||||
@@ -495,6 +518,60 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
},
|
||||
);
|
||||
|
||||
// GET /lighthouse/auth
|
||||
let get_auth = warp::path("lighthouse").and(warp::path("auth").and(warp::path::end()));
|
||||
let get_auth = get_auth
|
||||
.and(signer.clone())
|
||||
.and(api_token_path_filter)
|
||||
.and_then(|signer, token_path: PathBuf| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
Ok(AuthResponse {
|
||||
token_path: token_path.display().to_string(),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
// Standard key-manager endpoints.
|
||||
let eth_v1 = warp::path("eth").and(warp::path("v1"));
|
||||
let std_keystores = eth_v1.and(warp::path("keystores")).and(warp::path::end());
|
||||
|
||||
// GET /eth/v1/keystores
|
||||
let get_std_keystores = std_keystores
|
||||
.and(signer.clone())
|
||||
.and(validator_store_filter.clone())
|
||||
.and_then(|signer, validator_store: Arc<ValidatorStore<T, E>>| {
|
||||
blocking_signed_json_task(signer, move || Ok(keystores::list(validator_store)))
|
||||
});
|
||||
|
||||
// POST /eth/v1/keystores
|
||||
let post_std_keystores = std_keystores
|
||||
.and(warp::body::json())
|
||||
.and(signer.clone())
|
||||
.and(validator_dir_filter)
|
||||
.and(validator_store_filter.clone())
|
||||
.and(runtime_filter.clone())
|
||||
.and(log_filter.clone())
|
||||
.and_then(
|
||||
|request, signer, validator_dir, validator_store, runtime, log| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
keystores::import(request, validator_dir, validator_store, runtime, log)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// DELETE /eth/v1/keystores
|
||||
let delete_std_keystores = std_keystores
|
||||
.and(warp::body::json())
|
||||
.and(signer)
|
||||
.and(validator_store_filter)
|
||||
.and(runtime_filter)
|
||||
.and(log_filter)
|
||||
.and_then(|request, signer, validator_store, runtime, log| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
keystores::delete(request, validator_store, runtime, log)
|
||||
})
|
||||
});
|
||||
|
||||
let routes = warp::any()
|
||||
.and(authorization_header_filter)
|
||||
// Note: it is critical that the `authorization_header_filter` is applied to all routes.
|
||||
@@ -508,16 +585,21 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(get_lighthouse_health)
|
||||
.or(get_lighthouse_spec)
|
||||
.or(get_lighthouse_validators)
|
||||
.or(get_lighthouse_validators_pubkey),
|
||||
.or(get_lighthouse_validators_pubkey)
|
||||
.or(get_std_keystores),
|
||||
)
|
||||
.or(warp::post().and(
|
||||
post_validators
|
||||
.or(post_validators_keystore)
|
||||
.or(post_validators_mnemonic)
|
||||
.or(post_validators_web3signer),
|
||||
.or(post_validators_web3signer)
|
||||
.or(post_std_keystores),
|
||||
))
|
||||
.or(warp::patch().and(patch_validators)),
|
||||
.or(warp::patch().and(patch_validators))
|
||||
.or(warp::delete().and(delete_std_keystores)),
|
||||
)
|
||||
// The auth route is the only route that is allowed to be accessed without the API token.
|
||||
.or(warp::get().and(get_auth))
|
||||
// Maps errors into HTTP responses.
|
||||
.recover(warp_utils::reject::handle_rejection)
|
||||
// Add a `Server` header.
|
||||
@@ -550,7 +632,7 @@ pub async fn blocking_signed_json_task<S, F, T>(
|
||||
) -> Result<impl warp::Reply, warp::Rejection>
|
||||
where
|
||||
S: Fn(&[u8]) -> String,
|
||||
F: Fn() -> Result<T, warp::Rejection> + Send + 'static,
|
||||
F: FnOnce() -> Result<T, warp::Rejection> + Send + 'static,
|
||||
T: Serialize + Send + 'static,
|
||||
{
|
||||
warp_utils::task::blocking_task(func)
|
||||
|
||||
Reference in New Issue
Block a user