From 242ae21e5df875f04c8b9182d108aedfa1f6ae9f Mon Sep 17 00:00:00 2001 From: mariuspod <14898268+mariuspod@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:41:03 +0000 Subject: [PATCH] Pass EL JWT secret key via cli flag (#3568) ## Proposed Changes In this change I've added a new beacon_node cli flag `--execution-jwt-secret-key` for passing the JWT secret directly as string. Without this flag, it was non-trivial to pass a secrets file containing a JWT secret key without compromising its contents into some management repo or fiddling around with manual file mounts for cloud-based deployments. When used in combination with environment variables, the secret can be injected into container-based systems like docker & friends quite easily. It's both possible to either specify the file_path to the JWT secret or pass the JWT secret directly. I've modified the docs and attached a test as well. ## Additional Info The logic has been adapted a bit so that either one of `--execution-jwt` or `--execution-jwt-secret-key` must be set when specifying `--execution-endpoint` so that it's still compatible with the semantics before this change and there's at least one secret provided. --- beacon_node/src/cli.rs | 12 ++++++++++- beacon_node/src/config.rs | 35 +++++++++++++++++++++++++++------ book/src/merge-migration.md | 4 ++++ lighthouse/tests/beacon_node.rs | 23 +++++++++++++++++++++- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 51e8762f1..1e5184987 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -440,7 +440,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { JSON-RPC connection. Uses the same endpoint to populate the \ deposit cache.") .takes_value(true) - .requires("execution-jwt") ) .arg( Arg::with_name("execution-jwt") @@ -452,6 +451,17 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .requires("execution-endpoint") .takes_value(true) ) + .arg( + Arg::with_name("execution-jwt-secret-key") + .long("execution-jwt-secret-key") + .value_name("EXECUTION-JWT-SECRET-KEY") + .alias("jwt-secret-key") + .help("Hex-encoded JWT secret for the \ + execution endpoint provided in the --execution-endpoint flag.") + .requires("execution-endpoint") + .conflicts_with("execution-jwt") + .takes_value(true) + ) .arg( Arg::with_name("execution-jwt-id") .long("execution-jwt-id") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index f1d0fb35a..ecd4d736a 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -3,6 +3,7 @@ use clap_utils::flags::DISABLE_MALLOC_TUNING_FLAG; use client::{ClientConfig, ClientGenesis}; use directory::{DEFAULT_BEACON_NODE_DIR, DEFAULT_NETWORK_DIR, DEFAULT_ROOT_DIR}; use environment::RuntimeContext; +use execution_layer::DEFAULT_JWT_FILE; use genesis::Eth1Endpoint; use http_api::TlsConfig; use lighthouse_network::{multiaddr::Protocol, Enr, Multiaddr, NetworkConfig, PeerIdSerialized}; @@ -288,12 +289,34 @@ pub fn get_config( let execution_endpoint = parse_only_one_value(endpoints, SensitiveUrl::parse, "--execution-endpoint", log)?; - // Parse a single JWT secret, logging warnings if multiple are supplied. - // - // JWTs are required if `--execution-endpoint` is supplied. - let secret_files: String = clap_utils::parse_required(cli_args, "execution-jwt")?; - let secret_file = - parse_only_one_value(&secret_files, PathBuf::from_str, "--execution-jwt", log)?; + // JWTs are required if `--execution-endpoint` is supplied. They can be either passed via + // file_path or directly as string. + + let secret_file: PathBuf; + // Parse a single JWT secret from a given file_path, logging warnings if multiple are supplied. + if let Some(secret_files) = cli_args.value_of("execution-jwt") { + secret_file = + parse_only_one_value(secret_files, PathBuf::from_str, "--execution-jwt", log)?; + + // Check if the JWT secret key is passed directly via cli flag and persist it to the default + // file location. + } else if let Some(jwt_secret_key) = cli_args.value_of("execution-jwt-secret-key") { + use std::fs::File; + use std::io::Write; + secret_file = client_config.data_dir.join(DEFAULT_JWT_FILE); + let mut jwt_secret_key_file = File::create(secret_file.clone()) + .map_err(|e| format!("Error while creating jwt_secret_key file: {:?}", e))?; + jwt_secret_key_file + .write_all(jwt_secret_key.as_bytes()) + .map_err(|e| { + format!( + "Error occured while writing to jwt_secret_key file: {:?}", + e + ) + })?; + } else { + return Err("Error! Please set either --execution-jwt file_path or --execution-jwt-secret-key directly via cli when using --execution-endpoint".to_string()); + } // Parse and set the payload builder, if any. if let Some(endpoint) = cli_args.value_of("builder") { diff --git a/book/src/merge-migration.md b/book/src/merge-migration.md index 104a7ead6..780be5836 100644 --- a/book/src/merge-migration.md +++ b/book/src/merge-migration.md @@ -48,6 +48,10 @@ If you set up an execution engine with `--execution-endpoint` then you *must* pr using `--execution-jwt`. This is a mandatory form of authentication that ensures that Lighthouse has authority to control the execution engine. +> Tip: the --execution-jwt-secret-key flag can be used instead of --execution-jwt . +> This is useful, for example, for users who wish to inject the value into a Docker container without +> needing to pass a jwt secret file. + The execution engine connection must be **exclusive**, i.e. you must have one execution node per beacon node. The reason for this is that the beacon node _controls_ the execution node. Please see the [FAQ](#faq) for further information about why many:1 and 1:many configurations are not diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 2e76d832c..a00fd7a82 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -4,7 +4,7 @@ use crate::exec::{CommandLineTestExec, CompletedTest}; use eth1::Eth1Endpoint; use lighthouse_network::PeerId; use std::fs::File; -use std::io::Write; +use std::io::{Read, Write}; use std::net::IpAddr; use std::path::PathBuf; use std::process::Command; @@ -386,6 +386,27 @@ fn run_merge_execution_endpoints_flag_test(flag: &str) { }); } #[test] +fn run_execution_jwt_secret_key_is_persisted() { + let jwt_secret_key = "0x3cbc11b0d8fa16f3344eacfd6ff6430b9d30734450e8adcf5400f88d327dcb33"; + CommandLineTest::new() + .flag("execution-endpoint", Some("http://localhost:8551/")) + .flag("execution-jwt-secret-key", Some(jwt_secret_key)) + .run_with_zero_port() + .with_config(|config| { + let config = config.execution_layer.as_ref().unwrap(); + assert_eq!( + config.execution_endpoints[0].full.to_string(), + "http://localhost:8551/" + ); + let mut file_jwt_secret_key = String::new(); + File::open(config.secret_files[0].clone()) + .expect("could not open jwt_secret_key file") + .read_to_string(&mut file_jwt_secret_key) + .expect("could not read from file"); + assert_eq!(file_jwt_secret_key, jwt_secret_key); + }); +} +#[test] fn merge_execution_endpoints_flag() { run_merge_execution_endpoints_flag_test("execution-endpoints") }