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.
This commit is contained in:
Mac L 2021-10-15 00:07:11 +00:00
parent 05040e68ec
commit 7c23e2142a
5 changed files with 80 additions and 3 deletions

View File

@ -163,8 +163,10 @@ curl -X GET "https://localhost:5052/eth/v1/node/version" -H "accept: applicatio
``` ```
### Connecting a validator client ### Connecting a validator client
In order to connect a validator client to a beacon node over TLS, we need to In order to connect a validator client to a beacon node over TLS, the validator
add the certificate to the trust store of our operating system. 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. The process for this will vary depending on your operating system.
Below are the instructions for Ubuntu and Arch Linux: 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 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 ## Troubleshooting
### HTTP API is unavailable or refusing connections ### HTTP API is unavailable or refusing connections

View File

@ -202,6 +202,33 @@ fn use_long_timeouts_flag() {
.with_config(|config| assert!(config.use_long_timeouts)); .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. // Tests for Graffiti flags.
#[test] #[test]
fn graffiti_flag() { fn graffiti_flag() {

View File

@ -101,6 +101,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
made to the beacon node. This flag is generally not recommended, \ made to the beacon node. This flag is generally not recommended, \
longer timeouts can cause missed duties when fallbacks are used.") 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. // This overwrites the graffiti configured in the beacon node.
.arg( .arg(
Arg::with_name("graffiti") Arg::with_name("graffiti")

View File

@ -50,6 +50,9 @@ pub struct Config {
/// If true, enable functionality that monitors the network for attestations or proposals from /// If true, enable functionality that monitors the network for attestations or proposals from
/// any of the validators managed by this client before starting up. /// any of the validators managed by this client before starting up.
pub enable_doppelganger_protection: bool, 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<Vec<PathBuf>>,
} }
impl Default for Config { impl Default for Config {
@ -80,6 +83,7 @@ impl Default for Config {
http_metrics: <_>::default(), http_metrics: <_>::default(),
monitoring_api: None, monitoring_api: None,
enable_doppelganger_protection: false, enable_doppelganger_protection: false,
beacon_nodes_tls_certs: None,
} }
} }
} }
@ -193,6 +197,10 @@ impl Config {
} }
} }
if let Some(tls_certs) = parse_optional::<String>(cli_args, "beacon-nodes-tls-certs")? {
config.beacon_nodes_tls_certs = Some(tls_certs.split(',').map(PathBuf::from).collect());
}
/* /*
* Http API server * Http API server
*/ */

View File

@ -38,11 +38,15 @@ use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Timeouts};
use http_api::ApiSecret; use http_api::ApiSecret;
use notifier::spawn_notifier; use notifier::spawn_notifier;
use parking_lot::RwLock; use parking_lot::RwLock;
use reqwest::Certificate;
use slog::{error, info, warn, Logger}; use slog::{error, info, warn, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use slot_clock::SystemTimeSlotClock; use slot_clock::SystemTimeSlotClock;
use std::fs::File;
use std::io::Read;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use sync_committee_service::SyncCommitteeService; use sync_committee_service::SyncCommitteeService;
@ -246,7 +250,17 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
.map(|(i, url)| { .map(|(i, url)| {
let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot); 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. // Set default timeout to be the full slot duration.
.timeout(slot_duration) .timeout(slot_duration)
.build() .build()
@ -657,3 +671,12 @@ async fn poll_whilst_waiting_for_genesis<E: EthSpec>(
sleep(WAITING_FOR_GENESIS_POLL_TIME).await; sleep(WAITING_FOR_GENESIS_POLL_TIME).await;
} }
} }
pub fn load_pem_certificate<P: AsRef<Path>>(pem_path: P) -> Result<Certificate, String> {
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))
}