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
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

View File

@ -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() {

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, \
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")

View File

@ -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<Vec<PathBuf>>,
}
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::<String>(cli_args, "beacon-nodes-tls-certs")? {
config.beacon_nodes_tls_certs = Some(tls_certs.split(',').map(PathBuf::from).collect());
}
/*
* Http API server
*/

View File

@ -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<T: EthSpec> ProductionValidatorClient<T> {
.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<E: EthSpec>(
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))
}