mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 13:54:44 +00:00
Allow VC to create password files via HTTP API
This commit is contained in:
@@ -200,6 +200,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.required(false)
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("http-store-passwords-in-secrets-dir")
|
||||
.long("http-store-passwords-in-secrets-dir")
|
||||
.value_name("ORIGIN")
|
||||
.help("If present, any validators created via the HTTP will have keystore \
|
||||
passwords stored in the secrets-dir rather than the validator \
|
||||
definitions file.")
|
||||
.required(false)
|
||||
.takes_value(false),
|
||||
)
|
||||
/* Prometheus metrics HTTP server related arguments */
|
||||
.arg(
|
||||
Arg::with_name("metrics")
|
||||
|
||||
@@ -259,6 +259,10 @@ impl Config {
|
||||
config.http_api.allow_keystore_export = true;
|
||||
}
|
||||
|
||||
if cli_args.is_present("http-store-passwords-in-secrets-dir") {
|
||||
config.http_api.store_passwords_in_secrets_dir = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prometheus metrics HTTP server
|
||||
*/
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::ValidatorStore;
|
||||
use account_utils::validator_definitions::ValidatorDefinition;
|
||||
use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition};
|
||||
use account_utils::{
|
||||
eth2_wallet::{bip39::Mnemonic, WalletBuilder},
|
||||
random_mnemonic, random_password, ZeroizeString,
|
||||
};
|
||||
use eth2::lighthouse_vc::types::{self as api_types};
|
||||
use slot_clock::SlotClock;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use types::ChainSpec;
|
||||
use types::EthSpec;
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder};
|
||||
|
||||
/// Create some validator EIP-2335 keystores and store them on disk. Then, enroll the validators in
|
||||
/// this validator client.
|
||||
@@ -27,6 +27,7 @@ pub async fn create_validators_mnemonic<P: AsRef<Path>, T: 'static + SlotClock,
|
||||
key_derivation_path_offset: Option<u32>,
|
||||
validator_requests: &[api_types::ValidatorRequest],
|
||||
validator_dir: P,
|
||||
secrets_dir: Option<PathBuf>,
|
||||
validator_store: &ValidatorStore<T, E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(Vec<api_types::CreatedValidator>, Mnemonic), warp::Rejection> {
|
||||
@@ -95,7 +96,20 @@ pub async fn create_validators_mnemonic<P: AsRef<Path>, T: 'static + SlotClock,
|
||||
))
|
||||
})?;
|
||||
|
||||
let voting_password_storage = if let Some(secrets_dir) = &secrets_dir {
|
||||
let password_path = keystore_password_path(secrets_dir, &keystores.voting);
|
||||
if password_path.exists() {
|
||||
return Err(warp_utils::reject::custom_server_error(
|
||||
"Duplicate keystore password path".to_string(),
|
||||
));
|
||||
}
|
||||
PasswordStorage::File(password_path)
|
||||
} else {
|
||||
PasswordStorage::ValidatorDefinitions(voting_password_string.clone())
|
||||
};
|
||||
|
||||
let validator_dir = ValidatorDirBuilder::new(validator_dir.as_ref().into())
|
||||
.password_dir_opt(secrets_dir.clone())
|
||||
.voting_keystore(keystores.voting, voting_password.as_bytes())
|
||||
.withdrawal_keystore(keystores.withdrawal, withdrawal_password.as_bytes())
|
||||
.create_eth1_tx_data(request.deposit_gwei, spec)
|
||||
@@ -136,7 +150,7 @@ pub async fn create_validators_mnemonic<P: AsRef<Path>, T: 'static + SlotClock,
|
||||
validator_store
|
||||
.add_validator_keystore(
|
||||
voting_keystore_path,
|
||||
voting_password_string,
|
||||
voting_password_storage,
|
||||
request.enable,
|
||||
request.graffiti.clone(),
|
||||
request.suggested_fee_recipient,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
initialized_validators::Error, signing_method::SigningMethod, InitializedValidators,
|
||||
ValidatorStore,
|
||||
};
|
||||
use account_utils::ZeroizeString;
|
||||
use account_utils::{validator_definitions::PasswordStorage, ZeroizeString};
|
||||
use eth2::lighthouse_vc::{
|
||||
std_types::{
|
||||
DeleteKeystoreStatus, DeleteKeystoresRequest, DeleteKeystoresResponse,
|
||||
@@ -20,7 +20,7 @@ use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::runtime::Handle;
|
||||
use types::{EthSpec, PublicKeyBytes};
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder};
|
||||
use warp::Rejection;
|
||||
use warp_utils::reject::{custom_bad_request, custom_server_error};
|
||||
|
||||
@@ -61,6 +61,7 @@ pub fn list<T: SlotClock + 'static, E: EthSpec>(
|
||||
pub fn import<T: SlotClock + 'static, E: EthSpec>(
|
||||
request: ImportKeystoresRequest,
|
||||
validator_dir: PathBuf,
|
||||
secrets_dir: Option<PathBuf>,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
task_executor: TaskExecutor,
|
||||
log: Logger,
|
||||
@@ -131,6 +132,7 @@ pub fn import<T: SlotClock + 'static, E: EthSpec>(
|
||||
keystore,
|
||||
password,
|
||||
validator_dir.clone(),
|
||||
secrets_dir.clone(),
|
||||
&validator_store,
|
||||
handle,
|
||||
) {
|
||||
@@ -161,6 +163,7 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
keystore: Keystore,
|
||||
password: ZeroizeString,
|
||||
validator_dir_path: PathBuf,
|
||||
secrets_dir: Option<PathBuf>,
|
||||
validator_store: &ValidatorStore<T, E>,
|
||||
handle: Handle,
|
||||
) -> Result<ImportKeystoreStatus, String> {
|
||||
@@ -182,6 +185,16 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
}
|
||||
}
|
||||
|
||||
let password_storage = if let Some(secrets_dir) = &secrets_dir {
|
||||
let password_path = keystore_password_path(secrets_dir, &keystore);
|
||||
if password_path.exists() {
|
||||
return Ok(ImportKeystoreStatus::Duplicate);
|
||||
}
|
||||
PasswordStorage::File(password_path)
|
||||
} else {
|
||||
PasswordStorage::ValidatorDefinitions(password.clone())
|
||||
};
|
||||
|
||||
// Check that the password is correct.
|
||||
// In future we should re-structure to avoid the double decryption here. It's not as simple
|
||||
// as removing this check because `add_validator_keystore` will break if provided with an
|
||||
@@ -192,6 +205,7 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
.map_err(|e| format!("incorrect password: {:?}", e))?;
|
||||
|
||||
let validator_dir = ValidatorDirBuilder::new(validator_dir_path)
|
||||
.password_dir_opt(secrets_dir)
|
||||
.voting_keystore(keystore, password.as_ref())
|
||||
.store_withdrawal_keystore(false)
|
||||
.build()
|
||||
@@ -204,7 +218,7 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
handle
|
||||
.block_on(validator_store.add_validator_keystore(
|
||||
voting_keystore_path,
|
||||
password,
|
||||
password_storage,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -9,7 +9,9 @@ pub mod test_utils;
|
||||
use crate::ValidatorStore;
|
||||
use account_utils::{
|
||||
mnemonic_from_phrase,
|
||||
validator_definitions::{SigningDefinition, ValidatorDefinition, Web3SignerDefinition},
|
||||
validator_definitions::{
|
||||
PasswordStorage, SigningDefinition, ValidatorDefinition, Web3SignerDefinition,
|
||||
},
|
||||
};
|
||||
pub use api_secret::ApiSecret;
|
||||
use create_validator::{create_validators_mnemonic, create_validators_web3signer};
|
||||
@@ -28,7 +30,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use types::{ChainSpec, ConfigAndPreset, EthSpec};
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder};
|
||||
use warp::{
|
||||
http::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
@@ -64,6 +66,7 @@ pub struct Context<T: SlotClock, E: EthSpec> {
|
||||
pub api_secret: ApiSecret,
|
||||
pub validator_store: Option<Arc<ValidatorStore<T, E>>>,
|
||||
pub validator_dir: Option<PathBuf>,
|
||||
pub secrets_dir: Option<PathBuf>,
|
||||
pub spec: ChainSpec,
|
||||
pub config: Config,
|
||||
pub log: Logger,
|
||||
@@ -78,6 +81,7 @@ pub struct Config {
|
||||
pub listen_port: u16,
|
||||
pub allow_origin: Option<String>,
|
||||
pub allow_keystore_export: bool,
|
||||
pub store_passwords_in_secrets_dir: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -88,6 +92,7 @@ impl Default for Config {
|
||||
listen_port: 5062,
|
||||
allow_origin: None,
|
||||
allow_keystore_export: false,
|
||||
store_passwords_in_secrets_dir: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,6 +118,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
||||
let config = &ctx.config;
|
||||
let allow_keystore_export = config.allow_keystore_export;
|
||||
let store_passwords_in_secrets_dir = config.store_passwords_in_secrets_dir;
|
||||
let log = ctx.log.clone();
|
||||
|
||||
// Configure CORS.
|
||||
@@ -179,6 +185,17 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
})
|
||||
});
|
||||
|
||||
let inner_secrets_dir = ctx.secrets_dir.clone();
|
||||
let secrets_dir_filter = warp::any().map(move || inner_secrets_dir.clone()).and_then(
|
||||
|secrets_dir: Option<_>| async move {
|
||||
secrets_dir.ok_or_else(|| {
|
||||
warp_utils::reject::custom_not_found(
|
||||
"secrets_dir directory is not initialized.".to_string(),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let inner_ctx = ctx.clone();
|
||||
let log_filter = warp::any().map(move || inner_ctx.log.clone());
|
||||
|
||||
@@ -290,18 +307,21 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(validator_dir_filter.clone())
|
||||
.and(secrets_dir_filter.clone())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(spec_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and(task_executor_filter.clone())
|
||||
.and_then(
|
||||
|body: Vec<api_types::ValidatorRequest>,
|
||||
validator_dir: PathBuf,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
spec: Arc<ChainSpec>,
|
||||
signer,
|
||||
task_executor: TaskExecutor| {
|
||||
move |body: Vec<api_types::ValidatorRequest>,
|
||||
validator_dir: PathBuf,
|
||||
secrets_dir: PathBuf,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
spec: Arc<ChainSpec>,
|
||||
signer,
|
||||
task_executor: TaskExecutor| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
let secrets_dir = store_passwords_in_secrets_dir.then_some(secrets_dir);
|
||||
if let Some(handle) = task_executor.handle() {
|
||||
let (validators, mnemonic) =
|
||||
handle.block_on(create_validators_mnemonic(
|
||||
@@ -309,6 +329,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
None,
|
||||
&body,
|
||||
&validator_dir,
|
||||
secrets_dir,
|
||||
&validator_store,
|
||||
&spec,
|
||||
))?;
|
||||
@@ -333,18 +354,21 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(validator_dir_filter.clone())
|
||||
.and(secrets_dir_filter.clone())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(spec_filter)
|
||||
.and(signer.clone())
|
||||
.and(task_executor_filter.clone())
|
||||
.and_then(
|
||||
|body: api_types::CreateValidatorsMnemonicRequest,
|
||||
validator_dir: PathBuf,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
spec: Arc<ChainSpec>,
|
||||
signer,
|
||||
task_executor: TaskExecutor| {
|
||||
move |body: api_types::CreateValidatorsMnemonicRequest,
|
||||
validator_dir: PathBuf,
|
||||
secrets_dir: PathBuf,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
spec: Arc<ChainSpec>,
|
||||
signer,
|
||||
task_executor: TaskExecutor| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
let secrets_dir = store_passwords_in_secrets_dir.then_some(secrets_dir);
|
||||
if let Some(handle) = task_executor.handle() {
|
||||
let mnemonic =
|
||||
mnemonic_from_phrase(body.mnemonic.as_str()).map_err(|e| {
|
||||
@@ -359,6 +383,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
Some(body.key_derivation_path_offset),
|
||||
&body.validators,
|
||||
&validator_dir,
|
||||
secrets_dir,
|
||||
&validator_store,
|
||||
&spec,
|
||||
))?;
|
||||
@@ -379,15 +404,17 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(validator_dir_filter.clone())
|
||||
.and(secrets_dir_filter.clone())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and(task_executor_filter.clone())
|
||||
.and_then(
|
||||
|body: api_types::KeystoreValidatorsPostRequest,
|
||||
validator_dir: PathBuf,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
signer,
|
||||
task_executor: TaskExecutor| {
|
||||
move |body: api_types::KeystoreValidatorsPostRequest,
|
||||
validator_dir: PathBuf,
|
||||
secrets_dir: PathBuf,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
signer,
|
||||
task_executor: TaskExecutor| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
// Check to ensure the password is correct.
|
||||
let keypair = body
|
||||
@@ -400,7 +427,21 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
))
|
||||
})?;
|
||||
|
||||
let secrets_dir = store_passwords_in_secrets_dir.then(|| secrets_dir);
|
||||
let password_storage = if let Some(secrets_dir) = &secrets_dir {
|
||||
let password_path = keystore_password_path(secrets_dir, &body.keystore);
|
||||
if password_path.exists() {
|
||||
return Err(warp_utils::reject::custom_server_error(
|
||||
"Duplicate keystore password path".to_string(),
|
||||
));
|
||||
}
|
||||
PasswordStorage::File(password_path)
|
||||
} else {
|
||||
PasswordStorage::ValidatorDefinitions(body.password.clone())
|
||||
};
|
||||
|
||||
let validator_dir = ValidatorDirBuilder::new(validator_dir.clone())
|
||||
.password_dir_opt(secrets_dir)
|
||||
.voting_keystore(body.keystore.clone(), body.password.as_ref())
|
||||
.store_withdrawal_keystore(false)
|
||||
.build()
|
||||
@@ -414,7 +455,6 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
// Drop validator dir so that `add_validator_keystore` can re-lock the keystore.
|
||||
let voting_keystore_path = validator_dir.voting_keystore_path();
|
||||
drop(validator_dir);
|
||||
let voting_password = body.password.clone();
|
||||
let graffiti = body.graffiti.clone();
|
||||
let suggested_fee_recipient = body.suggested_fee_recipient;
|
||||
let gas_limit = body.gas_limit;
|
||||
@@ -425,7 +465,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
handle
|
||||
.block_on(validator_store.add_validator_keystore(
|
||||
voting_keystore_path,
|
||||
voting_password,
|
||||
password_storage,
|
||||
body.enable,
|
||||
graffiti,
|
||||
suggested_fee_recipient,
|
||||
@@ -850,13 +890,28 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::body::json())
|
||||
.and(signer.clone())
|
||||
.and(validator_dir_filter)
|
||||
.and(secrets_dir_filter)
|
||||
.and(validator_store_filter.clone())
|
||||
.and(task_executor_filter.clone())
|
||||
.and(log_filter.clone())
|
||||
.and_then(
|
||||
|request, signer, validator_dir, validator_store, task_executor, log| {
|
||||
move |request,
|
||||
signer,
|
||||
validator_dir,
|
||||
secrets_dir,
|
||||
validator_store,
|
||||
task_executor,
|
||||
log| {
|
||||
let secrets_dir = store_passwords_in_secrets_dir.then_some(secrets_dir);
|
||||
blocking_signed_json_task(signer, move || {
|
||||
keystores::import(request, validator_dir, validator_store, task_executor, log)
|
||||
keystores::import(
|
||||
request,
|
||||
validator_dir,
|
||||
secrets_dir,
|
||||
validator_store,
|
||||
task_executor,
|
||||
log,
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
@@ -121,6 +121,7 @@ impl ApiTester {
|
||||
task_executor: test_runtime.task_executor.clone(),
|
||||
api_secret,
|
||||
validator_dir: Some(validator_dir.path().into()),
|
||||
secrets_dir: Some(secrets_dir.path().into()),
|
||||
validator_store: Some(validator_store.clone()),
|
||||
spec: E::default_spec(),
|
||||
config: HttpConfig {
|
||||
@@ -129,6 +130,7 @@ impl ApiTester {
|
||||
listen_port: 0,
|
||||
allow_origin: None,
|
||||
allow_keystore_export: true,
|
||||
store_passwords_in_secrets_dir: false,
|
||||
},
|
||||
log,
|
||||
_phantom: PhantomData,
|
||||
|
||||
@@ -520,6 +520,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
||||
api_secret,
|
||||
validator_store: Some(self.validator_store.clone()),
|
||||
validator_dir: Some(self.config.validator_dir.clone()),
|
||||
secrets_dir: Some(self.config.secrets_dir.clone()),
|
||||
spec: self.context.eth2_config.spec.clone(),
|
||||
config: self.config.http_api.clone(),
|
||||
log: log.clone(),
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
signing_method::{Error as SigningError, SignableMessage, SigningContext, SigningMethod},
|
||||
Config,
|
||||
};
|
||||
use account_utils::{validator_definitions::ValidatorDefinition, ZeroizeString};
|
||||
use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use slashing_protection::{
|
||||
interchange::Interchange, InterchangeError, NotSafe, Safe, SlashingDatabase,
|
||||
@@ -161,7 +161,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
pub async fn add_validator_keystore<P: AsRef<Path>>(
|
||||
&self,
|
||||
voting_keystore_path: P,
|
||||
password: ZeroizeString,
|
||||
password_storage: PasswordStorage,
|
||||
enable: bool,
|
||||
graffiti: Option<GraffitiString>,
|
||||
suggested_fee_recipient: Option<Address>,
|
||||
@@ -170,7 +170,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
) -> Result<ValidatorDefinition, String> {
|
||||
let mut validator_def = ValidatorDefinition::new_keystore_with_password(
|
||||
voting_keystore_path,
|
||||
Some(password),
|
||||
password_storage,
|
||||
graffiti.map(Into::into),
|
||||
suggested_fee_recipient,
|
||||
gas_limit,
|
||||
|
||||
Reference in New Issue
Block a user