From 7c23e2142a54f9b0318b96769c6b8f5233b2e5fb Mon Sep 17 00:00:00 2001 From: Mac L Date: Fri, 15 Oct 2021 00:07:11 +0000 Subject: [PATCH] Allow custom certificates when connecting to BN (#2703) ## Issue Addressed Resolves #2262 ## Proposed Changes Add a new CLI flag `--beacon-nodes-tls-certs` which allows the user to specify a path to a certificate file (or a list of files, separated by commas). The VC will then use these certificates (in addition to the existing certificates in the OS trust store) when connecting to a beacon node over HTTPS. ## Additional Info This only supports certificates in PEM format. --- book/src/api-bn.md | 13 +++++++++++-- lighthouse/tests/validator_client.rs | 27 +++++++++++++++++++++++++++ validator_client/src/cli.rs | 10 ++++++++++ validator_client/src/config.rs | 8 ++++++++ validator_client/src/lib.rs | 25 ++++++++++++++++++++++++- 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/book/src/api-bn.md b/book/src/api-bn.md index bd1132f8ec..b82a8da1d0 100644 --- a/book/src/api-bn.md +++ b/book/src/api-bn.md @@ -163,8 +163,10 @@ curl -X GET "https://localhost:5052/eth/v1/node/version" -H "accept: applicatio ``` ### Connecting a validator client -In order to connect a validator client to a beacon node over TLS, we need to -add the certificate to the trust store of our operating system. +In order to connect a validator client to a beacon node over TLS, the validator +client needs to be aware of the certificate. +There are two ways to do this: +#### Option 1: Add the certificate to the operating system trust store The process for this will vary depending on your operating system. Below are the instructions for Ubuntu and Arch Linux: @@ -185,6 +187,13 @@ Now the validator client can be connected to the beacon node by running: lighthouse vc --beacon-nodes https://localhost:5052 ``` +#### Option 2: Specify the certificate via CLI +You can also specify any custom certificates via the validator client CLI like +so: +```bash +lighthouse vc --beacon-nodes https://localhost:5052 --beacon-nodes-tls-certs cert.pem +``` + ## Troubleshooting ### HTTP API is unavailable or refusing connections diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index 0fde9b901d..fc07492a90 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -202,6 +202,33 @@ fn use_long_timeouts_flag() { .with_config(|config| assert!(config.use_long_timeouts)); } +#[test] +fn beacon_nodes_tls_certs_flag() { + let dir = TempDir::new().expect("Unable to create temporary directory"); + CommandLineTest::new() + .flag( + "beacon-nodes-tls-certs", + Some( + vec![ + dir.path().join("certificate.crt").to_str().unwrap(), + dir.path().join("certificate2.crt").to_str().unwrap(), + ] + .join(",") + .as_str(), + ), + ) + .run() + .with_config(|config| { + assert_eq!( + config.beacon_nodes_tls_certs, + Some(vec![ + dir.path().join("certificate.crt"), + dir.path().join("certificate2.crt") + ]) + ) + }); +} + // Tests for Graffiti flags. #[test] fn graffiti_flag() { diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 386a20bf06..595cbb995a 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -101,6 +101,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { made to the beacon node. This flag is generally not recommended, \ longer timeouts can cause missed duties when fallbacks are used.") ) + .arg( + Arg::with_name("beacon-nodes-tls-certs") + .long("beacon-nodes-tls-certs") + .value_name("CERTIFICATE-FILES") + .takes_value(true) + .help("Comma-separated paths to custom TLS certificates to use when connecting \ + to a beacon node. These certificates must be in PEM format and are used \ + in addition to the OS trust store. Commas must only be used as a \ + delimiter, and must not be part of the certificate path.") + ) // This overwrites the graffiti configured in the beacon node. .arg( Arg::with_name("graffiti") diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 06f91e2bb9..4b07c72b8a 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -50,6 +50,9 @@ pub struct Config { /// If true, enable functionality that monitors the network for attestations or proposals from /// any of the validators managed by this client before starting up. pub enable_doppelganger_protection: bool, + /// A list of custom certificates that the validator client will additionally use when + /// connecting to a beacon node over SSL/TLS. + pub beacon_nodes_tls_certs: Option>, } impl Default for Config { @@ -80,6 +83,7 @@ impl Default for Config { http_metrics: <_>::default(), monitoring_api: None, enable_doppelganger_protection: false, + beacon_nodes_tls_certs: None, } } } @@ -193,6 +197,10 @@ impl Config { } } + if let Some(tls_certs) = parse_optional::(cli_args, "beacon-nodes-tls-certs")? { + config.beacon_nodes_tls_certs = Some(tls_certs.split(',').map(PathBuf::from).collect()); + } + /* * Http API server */ diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index ec72384278..a3ab10316a 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -38,11 +38,15 @@ use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Timeouts}; use http_api::ApiSecret; use notifier::spawn_notifier; use parking_lot::RwLock; +use reqwest::Certificate; use slog::{error, info, warn, Logger}; use slot_clock::SlotClock; use slot_clock::SystemTimeSlotClock; +use std::fs::File; +use std::io::Read; use std::marker::PhantomData; use std::net::SocketAddr; +use std::path::Path; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use sync_committee_service::SyncCommitteeService; @@ -246,7 +250,17 @@ impl ProductionValidatorClient { .map(|(i, url)| { let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot); - let beacon_node_http_client = ClientBuilder::new() + let mut beacon_node_http_client_builder = ClientBuilder::new(); + + // Add new custom root certificates if specified. + if let Some(certificates) = &config.beacon_nodes_tls_certs { + for cert in certificates { + beacon_node_http_client_builder = beacon_node_http_client_builder + .add_root_certificate(load_pem_certificate(cert)?); + } + } + + let beacon_node_http_client = beacon_node_http_client_builder // Set default timeout to be the full slot duration. .timeout(slot_duration) .build() @@ -657,3 +671,12 @@ async fn poll_whilst_waiting_for_genesis( sleep(WAITING_FOR_GENESIS_POLL_TIME).await; } } + +pub fn load_pem_certificate>(pem_path: P) -> Result { + let mut buf = Vec::new(); + File::open(&pem_path) + .map_err(|e| format!("Unable to open certificate path: {}", e))? + .read_to_end(&mut buf) + .map_err(|e| format!("Unable to read certificate file: {}", e))?; + Certificate::from_pem(&buf).map_err(|e| format!("Unable to parse certificate: {}", e)) +}