mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-31 13:17:09 +00:00
Prepare sensitive_url for crates.io (#8223)
Another good candidate for publishing separately from Lighthouse is `sensitive_url` as it's a general utility crate and not related to Ethereum. This PR prepares it to be spun out into its own crate. I've made the `full` field on `SensitiveUrl` private and instead provided an explicit getter called `.expose_full()`. It's a bit ugly for the diff but I prefer the explicit nature of the getter. I've also added some extra tests and doc strings along with feature gating `Serialize` and `Deserialize` implementations behind the `serde` feature. Co-Authored-By: Mac L <mjladson@pm.me>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -8485,6 +8485,7 @@ name = "sensitive_url"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ rpds = "0.11"
|
||||
rusqlite = { version = "0.28", features = ["bundled"] }
|
||||
rust_eth_kzg = "0.9"
|
||||
safe_arith = "0.1"
|
||||
sensitive_url = { path = "common/sensitive_url" }
|
||||
sensitive_url = { path = "common/sensitive_url", features = ["serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_repr = "0.1"
|
||||
|
||||
@@ -270,7 +270,7 @@ impl BuilderHttpClient {
|
||||
&self,
|
||||
validator: &[SignedValidatorRegistrationData],
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -289,7 +289,7 @@ impl BuilderHttpClient {
|
||||
&self,
|
||||
blinded_block: &SignedBlindedBeaconBlock<E>,
|
||||
) -> Result<FullPayloadContents<E>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
let body = blinded_block.as_ssz_bytes();
|
||||
|
||||
@@ -337,7 +337,7 @@ impl BuilderHttpClient {
|
||||
&self,
|
||||
blinded_block: &SignedBlindedBeaconBlock<E>,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
let body = blinded_block.as_ssz_bytes();
|
||||
|
||||
@@ -387,7 +387,7 @@ impl BuilderHttpClient {
|
||||
&self,
|
||||
blinded_block: &SignedBlindedBeaconBlock<E>,
|
||||
) -> Result<ForkVersionedResponse<FullPayloadContents<E>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -430,7 +430,7 @@ impl BuilderHttpClient {
|
||||
&self,
|
||||
blinded_block: &SignedBlindedBeaconBlock<E>,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -480,7 +480,7 @@ impl BuilderHttpClient {
|
||||
parent_hash: ExecutionBlockHash,
|
||||
pubkey: &PublicKeyBytes,
|
||||
) -> Result<Option<ForkVersionedResponse<SignedBuilderBid<E>>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -521,7 +521,7 @@ impl BuilderHttpClient {
|
||||
|
||||
/// `GET /eth/v1/builder/status`
|
||||
pub async fn get_builder_status<E: EthSpec>(&self) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
|
||||
@@ -652,7 +652,7 @@ impl HttpJsonRpc {
|
||||
|
||||
let mut request = self
|
||||
.client
|
||||
.post(self.url.full.clone())
|
||||
.post(self.url.expose_full().clone())
|
||||
.timeout(timeout)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.json(&body);
|
||||
|
||||
@@ -30,7 +30,7 @@ use reqwest::{
|
||||
};
|
||||
pub use reqwest::{StatusCode, Url};
|
||||
use reqwest_eventsource::{Event, EventSource};
|
||||
pub use sensitive_url::{SensitiveError, SensitiveUrl};
|
||||
pub use sensitive_url::SensitiveUrl;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use ssz::Encode;
|
||||
use std::fmt;
|
||||
@@ -152,12 +152,6 @@ impl fmt::Display for BeaconNodeHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for BeaconNodeHttpClient {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.server.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl BeaconNodeHttpClient {
|
||||
pub fn new(server: SensitiveUrl, timeouts: Timeouts) -> Self {
|
||||
Self {
|
||||
@@ -178,10 +172,14 @@ impl BeaconNodeHttpClient {
|
||||
timeouts,
|
||||
}
|
||||
}
|
||||
// Returns a reference to the `SensitiveUrl` of the server.
|
||||
pub fn server(&self) -> &SensitiveUrl {
|
||||
&self.server
|
||||
}
|
||||
|
||||
/// Return the path with the standard `/eth/vX` prefix applied.
|
||||
fn eth_path(&self, version: EndpointVersion) -> Result<Url, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -2613,7 +2611,7 @@ impl BeaconNodeHttpClient {
|
||||
ids: &[u64],
|
||||
epoch: Epoch,
|
||||
) -> Result<GenericResponse<Vec<LivenessResponseData>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
|
||||
@@ -173,7 +173,7 @@ pub struct DepositLog {
|
||||
impl BeaconNodeHttpClient {
|
||||
/// `GET lighthouse/health`
|
||||
pub async fn get_lighthouse_health(&self) -> Result<GenericResponse<Health>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -185,7 +185,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `GET lighthouse/syncing`
|
||||
pub async fn get_lighthouse_syncing(&self) -> Result<GenericResponse<SyncState>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -197,7 +197,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `GET lighthouse/custody/info`
|
||||
pub async fn get_lighthouse_custody_info(&self) -> Result<CustodyInfo, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -210,7 +210,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `POST lighthouse/custody/backfill`
|
||||
pub async fn post_lighthouse_custody_backfill(&self) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -231,7 +231,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `GET lighthouse/proto_array`
|
||||
pub async fn get_lighthouse_proto_array(&self) -> Result<GenericResponse<ProtoArray>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -246,7 +246,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
) -> Result<GenericResponse<GlobalValidatorInclusionData>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -264,7 +264,7 @@ impl BeaconNodeHttpClient {
|
||||
epoch: Epoch,
|
||||
validator_id: ValidatorId,
|
||||
) -> Result<GenericResponse<Option<ValidatorInclusionData>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -278,7 +278,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `POST lighthouse/database/reconstruct`
|
||||
pub async fn post_lighthouse_database_reconstruct(&self) -> Result<String, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -291,7 +291,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `POST lighthouse/add_peer`
|
||||
pub async fn post_lighthouse_add_peer(&self, req: AdminPeer) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -303,7 +303,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// `POST lighthouse/remove_peer`
|
||||
pub async fn post_lighthouse_remove_peer(&self, req: AdminPeer) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -323,7 +323,7 @@ impl BeaconNodeHttpClient {
|
||||
start_slot: Slot,
|
||||
end_slot: Slot,
|
||||
) -> Result<Vec<BlockReward>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -344,7 +344,7 @@ impl BeaconNodeHttpClient {
|
||||
start_epoch: Epoch,
|
||||
end_epoch: Epoch,
|
||||
) -> Result<Vec<BlockPackingEfficiency>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -366,7 +366,7 @@ impl BeaconNodeHttpClient {
|
||||
end_epoch: Epoch,
|
||||
target: String,
|
||||
) -> Result<Vec<AttestationPerformance>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
|
||||
@@ -283,7 +283,7 @@ impl ValidatorClientHttpClient {
|
||||
|
||||
/// `GET lighthouse/version`
|
||||
pub async fn get_lighthouse_version(&self) -> Result<GenericResponse<VersionData>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -295,7 +295,7 @@ impl ValidatorClientHttpClient {
|
||||
|
||||
/// `GET lighthouse/health`
|
||||
pub async fn get_lighthouse_health(&self) -> Result<GenericResponse<Health>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -309,7 +309,7 @@ impl ValidatorClientHttpClient {
|
||||
pub async fn get_lighthouse_spec<T: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
) -> Result<GenericResponse<T>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -323,7 +323,7 @@ impl ValidatorClientHttpClient {
|
||||
pub async fn get_lighthouse_validators(
|
||||
&self,
|
||||
) -> Result<GenericResponse<Vec<ValidatorData>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -338,7 +338,7 @@ impl ValidatorClientHttpClient {
|
||||
&self,
|
||||
validator_pubkey: &PublicKeyBytes,
|
||||
) -> Result<Option<GenericResponse<ValidatorData>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -354,7 +354,7 @@ impl ValidatorClientHttpClient {
|
||||
&self,
|
||||
validators: Vec<ValidatorRequest>,
|
||||
) -> Result<GenericResponse<PostValidatorsResponseData>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -369,7 +369,7 @@ impl ValidatorClientHttpClient {
|
||||
&self,
|
||||
request: &CreateValidatorsMnemonicRequest,
|
||||
) -> Result<GenericResponse<Vec<CreatedValidator>>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -385,7 +385,7 @@ impl ValidatorClientHttpClient {
|
||||
&self,
|
||||
request: &KeystoreValidatorsPostRequest,
|
||||
) -> Result<GenericResponse<ValidatorData>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -401,7 +401,7 @@ impl ValidatorClientHttpClient {
|
||||
&self,
|
||||
request: &[Web3SignerValidatorRequest],
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -424,7 +424,7 @@ impl ValidatorClientHttpClient {
|
||||
prefer_builder_proposals: Option<bool>,
|
||||
graffiti: Option<GraffitiString>,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -451,7 +451,7 @@ impl ValidatorClientHttpClient {
|
||||
&self,
|
||||
req: &DeleteKeystoresRequest,
|
||||
) -> Result<ExportKeystoresResponse, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -462,7 +462,7 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
fn make_keystores_url(&self) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
let mut url = self.server.expose_full().clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
@@ -472,7 +472,7 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
fn make_remotekeys_url(&self) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
let mut url = self.server.expose_full().clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
@@ -482,7 +482,7 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
fn make_fee_recipient_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
let mut url = self.server.expose_full().clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
@@ -494,7 +494,7 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
fn make_graffiti_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
let mut url = self.server.expose_full().clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
@@ -506,7 +506,7 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
fn make_gas_limit_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
let mut url = self.server.expose_full().clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
@@ -519,7 +519,7 @@ impl ValidatorClientHttpClient {
|
||||
|
||||
/// `GET lighthouse/auth`
|
||||
pub async fn get_auth(&self) -> Result<AuthResponse, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
let mut url = self.server.expose_full().clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("lighthouse")
|
||||
@@ -635,7 +635,7 @@ impl ValidatorClientHttpClient {
|
||||
pubkey: &PublicKeyBytes,
|
||||
epoch: Option<Epoch>,
|
||||
) -> Result<GenericResponse<SignedVoluntaryExit>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
let mut path = self.server.expose_full().clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
|
||||
@@ -195,7 +195,7 @@ impl MonitoringHttpClient {
|
||||
endpoint = %self.monitoring_endpoint,
|
||||
"Sending metrics to remote endpoint"
|
||||
);
|
||||
self.post(self.monitoring_endpoint.full.clone(), &metrics)
|
||||
self.post(self.monitoring_endpoint.expose_full().clone(), &metrics)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ authors = ["Mac L <mjladson@pm.me>"]
|
||||
edition = { workspace = true }
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,26 +1,69 @@
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
/// Errors that can occur when creating or parsing a `SensitiveUrl`.
|
||||
#[derive(Debug)]
|
||||
pub enum SensitiveError {
|
||||
pub enum Error {
|
||||
/// The URL cannot be used as a base URL.
|
||||
InvalidUrl(String),
|
||||
/// Failed to parse the URL string.
|
||||
ParseError(url::ParseError),
|
||||
/// Failed to redact sensitive information from the URL.
|
||||
RedactError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for SensitiveError {
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
match self {
|
||||
Error::InvalidUrl(msg) => write!(f, "Invalid URL: {}", msg),
|
||||
Error::ParseError(e) => write!(f, "Parse error: {}", e),
|
||||
Error::RedactError(msg) => write!(f, "Redact error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around Url which provides a custom `Display` implementation to protect user secrets.
|
||||
#[derive(Clone, PartialEq)]
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Error::ParseError(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A URL wrapper that redacts sensitive information in `Display` and `Debug` output.
|
||||
///
|
||||
/// This type stores both the full URL (with credentials, paths, and query parameters)
|
||||
/// and a redacted version (containing only the scheme, host, and port). The redacted
|
||||
/// version is used when displaying or debugging to prevent accidental leakage of
|
||||
/// credentials in logs.
|
||||
///
|
||||
/// Note that `SensitiveUrl` specifically does NOT implement `Deref`, meaning you cannot call
|
||||
/// `Url` methods like `.password()` or `.scheme()` directly on `SensitiveUrl`. You must first
|
||||
/// explicitly call `.expose_full()`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sensitive_url::SensitiveUrl;
|
||||
///
|
||||
/// let url = SensitiveUrl::parse("https://user:pass@example.com/api?token=secret").unwrap();
|
||||
///
|
||||
/// // Display shows only the redacted version:
|
||||
/// assert_eq!(url.to_string(), "https://example.com/");
|
||||
///
|
||||
/// // But you can still access the full URL when needed:
|
||||
/// let full = url.expose_full();
|
||||
/// assert_eq!(full.to_string(), "https://user:pass@example.com/api?token=secret");
|
||||
/// assert_eq!(full.password(), Some("pass"));
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SensitiveUrl {
|
||||
pub full: Url,
|
||||
pub redacted: String,
|
||||
full: Url,
|
||||
redacted: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for SensitiveUrl {
|
||||
@@ -31,16 +74,14 @@ impl fmt::Display for SensitiveUrl {
|
||||
|
||||
impl fmt::Debug for SensitiveUrl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.redacted.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for SensitiveUrl {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.redacted.as_str()
|
||||
f.debug_struct("SensitiveUrl")
|
||||
.field("redacted", &self.redacted)
|
||||
// Maintains traditional `Debug` format but hides the 'full' field.
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for SensitiveUrl {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
@@ -50,6 +91,7 @@ impl Serialize for SensitiveUrl {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> Deserialize<'de> for SensitiveUrl {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
@@ -62,7 +104,7 @@ impl<'de> Deserialize<'de> for SensitiveUrl {
|
||||
}
|
||||
|
||||
impl FromStr for SensitiveUrl {
|
||||
type Err = SensitiveError;
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::parse(s)
|
||||
@@ -70,26 +112,28 @@ impl FromStr for SensitiveUrl {
|
||||
}
|
||||
|
||||
impl SensitiveUrl {
|
||||
pub fn parse(url: &str) -> Result<Self, SensitiveError> {
|
||||
let surl = Url::parse(url).map_err(SensitiveError::ParseError)?;
|
||||
/// Attempts to parse a `&str` into a `SensitiveUrl`.
|
||||
pub fn parse(url: &str) -> Result<Self, Error> {
|
||||
let surl = Url::parse(url).map_err(Error::ParseError)?;
|
||||
SensitiveUrl::new(surl)
|
||||
}
|
||||
|
||||
pub fn new(full: Url) -> Result<Self, SensitiveError> {
|
||||
/// Creates a `SensitiveUrl` from an existing `Url`.
|
||||
pub fn new(full: Url) -> Result<Self, Error> {
|
||||
let mut redacted = full.clone();
|
||||
redacted
|
||||
.path_segments_mut()
|
||||
.map_err(|_| SensitiveError::InvalidUrl("URL cannot be a base.".to_string()))?
|
||||
.map_err(|_| Error::InvalidUrl("URL cannot be a base.".to_string()))?
|
||||
.clear();
|
||||
redacted.set_query(None);
|
||||
|
||||
if redacted.has_authority() {
|
||||
redacted.set_username("").map_err(|_| {
|
||||
SensitiveError::RedactError("Unable to redact username.".to_string())
|
||||
})?;
|
||||
redacted.set_password(None).map_err(|_| {
|
||||
SensitiveError::RedactError("Unable to redact password.".to_string())
|
||||
})?;
|
||||
redacted
|
||||
.set_username("")
|
||||
.map_err(|_| Error::RedactError("Unable to redact username.".to_string()))?;
|
||||
redacted
|
||||
.set_password(None)
|
||||
.map_err(|_| Error::RedactError("Unable to redact password.".to_string()))?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
@@ -97,6 +141,16 @@ impl SensitiveUrl {
|
||||
redacted: redacted.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the full, unredacted URL.
|
||||
pub fn expose_full(&self) -> &Url {
|
||||
&self.full
|
||||
}
|
||||
|
||||
/// Returns the redacted URL as a `&str`.
|
||||
pub fn redacted(&self) -> &str {
|
||||
&self.redacted
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -105,16 +159,81 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn redact_remote_url() {
|
||||
let full = "https://project:secret@example.com/example?somequery";
|
||||
let full = "https://user:pass@example.com/example?somequery";
|
||||
let surl = SensitiveUrl::parse(full).unwrap();
|
||||
assert_eq!(surl.to_string(), "https://example.com/");
|
||||
assert_eq!(surl.full.to_string(), full);
|
||||
assert_eq!(surl.expose_full().to_string(), full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redact_localhost_url() {
|
||||
let full = "http://localhost:5052/";
|
||||
let full = "http://user:pass@localhost:5052/";
|
||||
let surl = SensitiveUrl::parse(full).unwrap();
|
||||
assert_eq!(surl.to_string(), "http://localhost:5052/");
|
||||
assert_eq!(surl.full.to_string(), full);
|
||||
assert_eq!(surl.expose_full().to_string(), full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_credentials() {
|
||||
let full = "https://example.com/path";
|
||||
let surl = SensitiveUrl::parse(full).unwrap();
|
||||
assert_eq!(surl.to_string(), "https://example.com/");
|
||||
assert_eq!(surl.expose_full().to_string(), full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let full = "https://user:pass@example.com/api?token=secret";
|
||||
let surl = SensitiveUrl::parse(full).unwrap();
|
||||
|
||||
let display = surl.to_string();
|
||||
assert_eq!(display, "https://example.com/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let full = "https://user:pass@example.com/api?token=secret";
|
||||
let surl = SensitiveUrl::parse(full).unwrap();
|
||||
|
||||
let debug = format!("{:?}", surl);
|
||||
|
||||
assert_eq!(
|
||||
debug,
|
||||
"SensitiveUrl { redacted: \"https://example.com/\", .. }"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let full = "https://user:pass@example.com/api?token=secret";
|
||||
let surl = SensitiveUrl::parse(full).unwrap();
|
||||
|
||||
let json = serde_json::to_string(&surl).unwrap();
|
||||
assert_eq!(json, format!("\"{}\"", full));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize() {
|
||||
let full = "https://user:pass@example.com/api?token=secret";
|
||||
let json = format!("\"{}\"", full);
|
||||
|
||||
let surl: SensitiveUrl = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(surl.expose_full().as_str(), full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip() {
|
||||
let full = "https://user:pass@example.com/api?token=secret";
|
||||
let original = SensitiveUrl::parse(full).unwrap();
|
||||
|
||||
let json = serde_json::to_string(&original).unwrap();
|
||||
let deserialized: SensitiveUrl = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(deserialized.expose_full(), original.expose_full());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,7 +481,12 @@ fn run_execution_jwt_secret_key_is_persisted() {
|
||||
.with_config(|config| {
|
||||
let config = config.execution_layer.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
config.execution_endpoint.as_ref().unwrap().full.to_string(),
|
||||
config
|
||||
.execution_endpoint
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.expose_full()
|
||||
.to_string(),
|
||||
"http://localhost:8551/"
|
||||
);
|
||||
let mut file_jwt_secret_key = String::new();
|
||||
@@ -532,7 +537,12 @@ fn bellatrix_jwt_secrets_flag() {
|
||||
.with_config(|config| {
|
||||
let config = config.execution_layer.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
config.execution_endpoint.as_ref().unwrap().full.to_string(),
|
||||
config
|
||||
.execution_endpoint
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.expose_full()
|
||||
.to_string(),
|
||||
"http://localhost:8551/"
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -109,12 +109,12 @@ fn beacon_nodes_flag() {
|
||||
.run()
|
||||
.with_config(|config| {
|
||||
assert_eq!(
|
||||
config.beacon_nodes[0].full.to_string(),
|
||||
config.beacon_nodes[0].expose_full().to_string(),
|
||||
"http://localhost:1001/"
|
||||
);
|
||||
assert_eq!(config.beacon_nodes[0].to_string(), "http://localhost:1001/");
|
||||
assert_eq!(
|
||||
config.beacon_nodes[1].full.to_string(),
|
||||
config.beacon_nodes[1].expose_full().to_string(),
|
||||
"https://project:secret@infura.io/"
|
||||
);
|
||||
assert_eq!(config.beacon_nodes[1].to_string(), "https://infura.io/");
|
||||
|
||||
@@ -64,7 +64,7 @@ async fn import_and_unlock(http_url: SensitiveUrl, priv_keys: &[&str], password:
|
||||
|
||||
let client = Client::builder().build().unwrap();
|
||||
let request = client
|
||||
.post(http_url.full.clone())
|
||||
.post(http_url.expose_full().clone())
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.json(&body);
|
||||
|
||||
@@ -90,7 +90,7 @@ async fn import_and_unlock(http_url: SensitiveUrl, priv_keys: &[&str], password:
|
||||
);
|
||||
|
||||
let request = client
|
||||
.post(http_url.full.clone())
|
||||
.post(http_url.expose_full().clone())
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.json(&body);
|
||||
|
||||
|
||||
@@ -656,7 +656,7 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
R: Future<Output = Result<O, Err>>,
|
||||
Err: Debug,
|
||||
{
|
||||
inc_counter_vec(&ENDPOINT_REQUESTS, &[candidate.as_ref()]);
|
||||
inc_counter_vec(&ENDPOINT_REQUESTS, &[candidate.server().redacted()]);
|
||||
|
||||
// There exists a race condition where `func` may be called when the candidate is
|
||||
// actually not ready. We deem this an acceptable inefficiency.
|
||||
@@ -668,7 +668,7 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
error = ?e,
|
||||
"Request to beacon node failed"
|
||||
);
|
||||
inc_counter_vec(&ENDPOINT_ERRORS, &[candidate.as_ref()]);
|
||||
inc_counter_vec(&ENDPOINT_ERRORS, &[candidate.server().redacted()]);
|
||||
Err((candidate.to_string(), Error::RequestFailed(e)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +191,7 @@ async fn run<E: EthSpec>(config: ExitConfig) -> Result<(), String> {
|
||||
// Only publish the voluntary exit if the --beacon-node flag is present
|
||||
if let Some(ref beacon_url) = beacon_url {
|
||||
let beacon_node = BeaconNodeHttpClient::new(
|
||||
SensitiveUrl::parse(beacon_url.as_ref())
|
||||
.map_err(|e| format!("Failed to parse beacon http server: {:?}", e))?,
|
||||
beacon_url.clone(),
|
||||
Timeouts::set_all(Duration::from_secs(12)),
|
||||
);
|
||||
|
||||
@@ -399,7 +398,7 @@ mod test {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let beacon_url = SensitiveUrl::parse(self.beacon_node.client.as_ref()).unwrap();
|
||||
let beacon_url = self.beacon_node.client.server().clone();
|
||||
|
||||
let validators_to_exit = index_of_validators_to_exit
|
||||
.iter()
|
||||
|
||||
@@ -134,8 +134,7 @@ async fn run<E: EthSpec>(config: ListConfig) -> Result<Vec<SingleKeystoreRespons
|
||||
if let Some(ref beacon_url) = beacon_url {
|
||||
for validator in &validators_to_display {
|
||||
let beacon_node = BeaconNodeHttpClient::new(
|
||||
SensitiveUrl::parse(beacon_url.as_ref())
|
||||
.map_err(|e| format!("Failed to parse beacon http server: {:?}", e))?,
|
||||
beacon_url.clone(),
|
||||
Timeouts::set_all(Duration::from_secs(12)),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user