Add client authentication to Web3Signer validators (#3170)

## Issue Addressed

Web3Signer validators do not support client authentication. This means the `--tls-known-clients-file` option on Web3Signer can't be used with Lighthouse.

## Proposed Changes

Add two new fields to Web3Signer validators, `client_identity_path` and `client_identity_password`, which specify the path and password for a PKCS12 file containing a certificate and private key. If `client_identity_path` is present, use the certificate for SSL client authentication.

## Additional Info

I am successfully validating on Prater using client authentication with Web3Signer and client authentication.
This commit is contained in:
Peter Davies
2022-05-18 23:14:37 +00:00
parent 053625f113
commit 807283538f
25 changed files with 316 additions and 95 deletions

View File

@@ -18,7 +18,7 @@ use eth2_keystore::Keystore;
use lighthouse_metrics::set_gauge;
use lockfile::{Lockfile, LockfileError};
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use reqwest::{Certificate, Client, Error as ReqwestError};
use reqwest::{Certificate, Client, Error as ReqwestError, Identity};
use slog::{debug, error, info, warn, Logger};
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
@@ -88,6 +88,11 @@ pub enum Error {
/// Unable to read the root certificate file for the remote signer.
InvalidWeb3SignerRootCertificateFile(io::Error),
InvalidWeb3SignerRootCertificate(ReqwestError),
/// Unable to read the client certificate for the remote signer.
MissingWeb3SignerClientIdentityCertificateFile,
MissingWeb3SignerClientIdentityPassword,
InvalidWeb3SignerClientIdentityCertificateFile(io::Error),
InvalidWeb3SignerClientIdentityCertificate(ReqwestError),
UnableToBuildWeb3SignerClient(ReqwestError),
/// Unable to apply an action to a validator.
InvalidActionOnValidator,
@@ -238,6 +243,8 @@ impl InitializedValidator {
url,
root_certificate_path,
request_timeout_ms,
client_identity_path,
client_identity_password,
} => {
let signing_url = build_web3_signer_url(&url, &def.voting_public_key)
.map_err(|e| Error::InvalidWeb3SignerUrl(e.to_string()))?;
@@ -254,6 +261,20 @@ impl InitializedValidator {
builder
};
let builder = if let Some(path) = client_identity_path {
let identity = load_pkcs12_identity(
path,
&client_identity_password
.ok_or(Error::MissingWeb3SignerClientIdentityPassword)?,
)?;
builder.identity(identity)
} else {
if client_identity_password.is_some() {
return Err(Error::MissingWeb3SignerClientIdentityCertificateFile);
}
builder
};
let http_client = builder
.build()
.map_err(Error::UnableToBuildWeb3SignerClient)?;
@@ -294,6 +315,19 @@ pub fn load_pem_certificate<P: AsRef<Path>>(pem_path: P) -> Result<Certificate,
Certificate::from_pem(&buf).map_err(Error::InvalidWeb3SignerRootCertificate)
}
pub fn load_pkcs12_identity<P: AsRef<Path>>(
pkcs12_path: P,
password: &str,
) -> Result<Identity, Error> {
let mut buf = Vec::new();
File::open(&pkcs12_path)
.map_err(Error::InvalidWeb3SignerClientIdentityCertificateFile)?
.read_to_end(&mut buf)
.map_err(Error::InvalidWeb3SignerClientIdentityCertificateFile)?;
Identity::from_pkcs12_der(&buf, password)
.map_err(Error::InvalidWeb3SignerClientIdentityCertificate)
}
fn build_web3_signer_url(base_url: &str, voting_public_key: &PublicKey) -> Result<Url, ParseError> {
Url::parse(base_url)?.join(&format!("api/v1/eth2/sign/{}", voting_public_key))
}