Make using insecure connections opt-in (#1309)

* add insecure url checks

* rename variables

* add feature flags to expose Url properly

* fix test compile error

* fix feature errors

* remove comment

* add url crate and use it for url parsing

* fix compile errors

* satisfy the holy clippy

* fix typos and host loopback

* macro attribute, provide validation function in utils

* fix expected output of ui tests

* remove the success case for --allow-insecure because we cannot establish ws:// connection at the moment.
This commit is contained in:
Tadeo Hepperle
2024-01-09 18:18:23 +01:00
committed by GitHub
parent 5b35a9f849
commit 7f714cbcb9
22 changed files with 562 additions and 413 deletions
+11
View File
@@ -20,7 +20,18 @@ pub struct RpcClient {
impl RpcClient {
#[cfg(feature = "jsonrpsee")]
/// Create a default RPC client pointed at some URL, currently based on [`jsonrpsee`].
///
/// Errors if an insecure URL is provided. In this case, use [`RpcClient::from_insecure_url`] instead.
pub async fn from_url<U: AsRef<str>>(url: U) -> Result<Self, Error> {
crate::utils::validate_url_is_secure(url.as_ref())?;
RpcClient::from_insecure_url(url).await
}
#[cfg(feature = "jsonrpsee")]
/// Create a default RPC client pointed at some URL, currently based on [`jsonrpsee`].
///
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
pub async fn from_insecure_url<U: AsRef<str>>(url: U) -> Result<Self, Error> {
let client = jsonrpsee_helpers::client(url.as_ref())
.await
.map_err(|e| crate::error::RpcError::ClientError(Box::new(e)))?;
+14 -2
View File
@@ -5,6 +5,8 @@
use super::{rpc::LightClientRpc, LightClient, LightClientError};
use crate::backend::rpc::RpcClient;
use crate::client::RawLightClient;
use crate::error::RpcError;
use crate::utils::validate_url_is_secure;
use crate::{config::Config, error::Error, OnlineClient};
use std::num::NonZeroU32;
use subxt_lightclient::{smoldot, AddedChain};
@@ -101,8 +103,19 @@ impl<T: Config> LightClientBuilder<T> {
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
#[cfg(feature = "jsonrpsee")]
pub async fn build_from_url<Url: AsRef<str>>(self, url: Url) -> Result<LightClient<T>, Error> {
let chain_spec = fetch_url(url.as_ref()).await?;
validate_url_is_secure(url.as_ref())?;
self.build_from_insecure_url(url).await
}
/// Build the light client with specified URL to connect to. Allows insecure URLs (no SSL, ws:// or http://).
///
/// For secure connections only, please use [`crate::LightClientBuilder::build_from_url`].
#[cfg(feature = "jsonrpsee")]
pub async fn build_from_insecure_url<Url: AsRef<str>>(
self,
url: Url,
) -> Result<LightClient<T>, Error> {
let chain_spec = fetch_url(url.as_ref()).await?;
self.build_client(chain_spec).await
}
@@ -235,7 +248,6 @@ async fn build_client_from_rpc<T: Config>(
#[cfg(feature = "jsonrpsee")]
async fn fetch_url(url: impl AsRef<str>) -> Result<serde_json::Value, Error> {
use jsonrpsee::core::client::ClientT;
let client = jsonrpsee_helpers::client(url.as_ref()).await?;
client
+9 -1
View File
@@ -66,7 +66,15 @@ impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`], providing a URL to connect to.
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
let client = RpcClient::from_url(url).await?;
crate::utils::validate_url_is_secure(url.as_ref())?;
OnlineClient::from_insecure_url(url).await
}
/// Construct a new [`OnlineClient`], providing a URL to connect to.
///
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
pub async fn from_insecure_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
let client = RpcClient::from_insecure_url(url).await?;
let backend = LegacyBackend::new(client);
OnlineClient::from_backend(Arc::new(backend)).await
}
+3
View File
@@ -115,6 +115,9 @@ pub enum RpcError {
/// The RPC subscription dropped.
#[error("RPC error: subscription dropped.")]
SubscriptionDropped,
/// The requested URL is insecure.
#[error("RPC error: insecure URL: {0}")]
InsecureUrl(String),
}
impl RpcError {
+4 -4
View File
@@ -220,7 +220,7 @@ pub mod ext {
/// mod polkadot {}
/// ```
///
/// ## `runtime_metadata_url = "..."`
/// ## `runtime_metadata_insecure_url = "..."`
///
/// This attribute can be used instead of `runtime_metadata_path` and will tell the macro to download metadata from a node running
/// at the provided URL, rather than a node running locally. This can be useful in CI, but is **not recommended** in production code,
@@ -228,7 +228,7 @@ pub mod ext {
///
/// ```rust,ignore
/// #[subxt::subxt(
/// runtime_metadata_url = "wss://rpc.polkadot.io:443"
/// runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443"
/// )]
/// mod polkadot {}
/// ```
@@ -281,14 +281,14 @@ pub mod ext {
///
/// ## `unstable_metadata`
///
/// This attribute works only in combination with `runtime_metadata_url`. By default, the macro will fetch the latest stable
/// This attribute works only in combination with `runtime_metadata_insecure_url`. By default, the macro will fetch the latest stable
/// version of the metadata from the target node. This attribute makes the codegen attempt to fetch the unstable version of
/// the metadata first. This is **not recommended** in production code, since the unstable metadata a node is providing is likely
/// to be incompatible with Subxt.
///
/// ```rust,ignore
/// #[subxt::subxt(
/// runtime_metadata_url = "wss://rpc.polkadot.io:443",
/// runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443",
/// unstable_metadata
/// )]
/// mod polkadot {}
+28
View File
@@ -13,8 +13,11 @@ mod static_type;
mod unchecked_extrinsic;
mod wrapper_opaque;
use crate::error::RpcError;
use crate::Error;
use codec::{Compact, Decode, Encode};
use derivative::Derivative;
use url::Url;
pub use account_id::AccountId32;
pub use era::Era;
@@ -47,6 +50,31 @@ pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::
Ok((val.0, *cursor))
}
/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost.
///
/// Returns an error if the the string could not be parsed into a URL.
pub fn url_is_secure(url: &str) -> Result<bool, Error> {
let url = Url::parse(url).map_err(|e| Error::Rpc(RpcError::ClientError(Box::new(e))))?;
let secure_scheme = url.scheme() == "https" || url.scheme() == "wss";
let is_localhost = url.host().is_some_and(|e| match e {
url::Host::Domain(e) => e == "localhost",
url::Host::Ipv4(e) => e.is_loopback(),
url::Host::Ipv6(e) => e.is_loopback(),
});
Ok(secure_scheme || is_localhost)
}
/// Validates, that the given Url is secure ("https" or "wss" scheme) or is referring to localhost.
pub fn validate_url_is_secure(url: &str) -> Result<(), Error> {
if !url_is_secure(url)? {
Err(Error::Rpc(crate::error::RpcError::InsecureUrl(url.into())))
} else {
Ok(())
}
}
/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).