Skip to content

Commit

Permalink
feat(sdk): allow setting CA cert to use when connecting to servers
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek committed Aug 19, 2024
1 parent 7accd40 commit 431ec00
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 22 deletions.
2 changes: 2 additions & 0 deletions packages/rs-dapi-client/src/dapi_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl DapiRequestExecutor for DapiClient {
// Join settings of different sources to get final version of the settings for this execution:
let applied_settings = self
.settings
.clone()
.override_by(R::SETTINGS_OVERRIDES)
.override_by(settings)
.finalize();
Expand All @@ -151,6 +152,7 @@ impl DapiRequestExecutor for DapiClient {
// Setup DAPI request execution routine future. It's a closure that will be called
// more once to build new future on each retry.
let routine = move || {
let applied_settings = applied_settings.clone();
// Try to get an address to initialize transport on:

let address_list = self
Expand Down
23 changes: 20 additions & 3 deletions packages/rs-dapi-client/src/request_settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! DAPI client request settings processing.

use std::time::Duration;
use dapi_grpc::tonic::transport::Certificate;
use std::{fs::read_to_string, path::Path, time::Duration};

/// Default low-level client timeout
const DEFAULT_CONNECT_TIMEOUT: Option<Duration> = None;
Expand All @@ -15,7 +16,7 @@ const DEFAULT_BAN_FAILED_ADDRESS: bool = true;
/// 2. [crate::DapiClient] settings;
/// 3. [crate::DapiRequest]-specific settings;
/// 4. settings for an exact request execution call.
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Default)]
pub struct RequestSettings {
/// Timeout for establishing a connection.
pub connect_timeout: Option<Duration>,
Expand All @@ -25,6 +26,8 @@ pub struct RequestSettings {
pub retries: Option<usize>,
/// Ban DAPI address if node not responded or responded with error.
pub ban_failed_address: Option<bool>,
/// Certificate Authority certificate to use for verifying the server's certificate.
pub ca_certificate: Option<Certificate>,
}

impl RequestSettings {
Expand All @@ -36,6 +39,7 @@ impl RequestSettings {
timeout: None,
retries: None,
ban_failed_address: None,
ca_certificate: None,
}
}

Expand All @@ -48,6 +52,7 @@ impl RequestSettings {
timeout: rhs.timeout.or(self.timeout),
retries: rhs.retries.or(self.retries),
ban_failed_address: rhs.ban_failed_address.or(self.ban_failed_address),
ca_certificate: rhs.ca_certificate.or(self.ca_certificate),
}
}

Expand All @@ -60,12 +65,22 @@ impl RequestSettings {
ban_failed_address: self
.ban_failed_address
.unwrap_or(DEFAULT_BAN_FAILED_ADDRESS),
ca_certificate: self.ca_certificate,
}
}

/// Load a certificate from a file and set it as a CA certificate.
pub fn with_ca_certificate(mut self, path: impl AsRef<Path>) -> std::io::Result<Self> {
let cert_bytes = read_to_string(path)?;
let cert = Certificate::from_pem(cert_bytes);

self.ca_certificate = Some(cert);
Ok(self)
}
}

/// DAPI settings ready to use.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct AppliedRequestSettings {
/// Timeout for establishing a connection.
pub connect_timeout: Option<Duration>,
Expand All @@ -75,4 +90,6 @@ pub struct AppliedRequestSettings {
pub retries: usize,
/// Ban DAPI address if node not responded or responded with error.
pub ban_failed_address: bool,
/// Certificate Authority certificate to use for verifying the server's certificate.
pub ca_certificate: Option<Certificate>,
}
26 changes: 21 additions & 5 deletions packages/rs-dapi-client/src/transport/grpc.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
//! Listing of gRPC requests used in DAPI.

use std::time::Duration;

use super::{CanRetry, TransportClient, TransportRequest};
use crate::connection_pool::{ConnectionPool, PoolPrefix};
use crate::{request_settings::AppliedRequestSettings, RequestSettings};
use dapi_grpc::core::v0::core_client::CoreClient;
use dapi_grpc::core::v0::{self as core_proto};
use dapi_grpc::platform::v0::{self as platform_proto, platform_client::PlatformClient};
use dapi_grpc::tonic::transport::ClientTlsConfig;
use dapi_grpc::tonic::transport::Uri;
use dapi_grpc::tonic::Streaming;
use dapi_grpc::tonic::{transport::Channel, IntoRequest};
use futures::{future::BoxFuture, FutureExt, TryFutureExt};
use std::time::Duration;

/// Platform Client using gRPC transport.
pub type PlatformGrpcClient = PlatformClient<Channel>;
/// Core Client using gRPC transport.
pub type CoreGrpcClient = CoreClient<Channel>;

fn create_channel(uri: Uri, settings: Option<&AppliedRequestSettings>) -> Channel {
let host = uri.host().expect("Failed to get host from URI").to_string();

let mut builder = Channel::builder(uri);

if let Some(settings) = settings {
if let Some(timeout) = settings.connect_timeout {
builder = builder.connect_timeout(timeout);
}
if let Some(cert) = settings.ca_certificate.as_ref() {
let tls_config = ClientTlsConfig::new()
.domain_name(host)
.ca_certificate(cert.clone());
builder = builder
.tls_config(tls_config)
.expect("Failed to set TLS config");
}
}

builder.connect_lazy()
Expand Down Expand Up @@ -186,8 +196,11 @@ impl_transport_request_grpc!(
platform_proto::WaitForStateTransitionResultResponse,
PlatformGrpcClient,
RequestSettings {
timeout: Some(Duration::from_secs(120)),
..RequestSettings::default()
timeout: Some(Duration::from_secs(80)),
retries: Some(0),
ca_certificate: None,
ban_failed_address: None,
connect_timeout: None,
},
wait_for_state_transition_result
);
Expand Down Expand Up @@ -382,7 +395,10 @@ impl_transport_request_grpc!(
CoreGrpcClient,
RequestSettings {
timeout: Some(STREAMING_TIMEOUT),
..RequestSettings::default()
ca_certificate: None,
ban_failed_address: None,
connect_timeout: None,
retries: None,
},
subscribe_to_transactions_with_proofs
);
5 changes: 4 additions & 1 deletion packages/rs-dapi-client/tests/mock_dapi_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ async fn test_mock_get_identity_dapi_client() {

let settings = RequestSettings::default();

let result = dapi.execute(request.clone(), settings).await.unwrap();
let result = dapi
.execute(request.clone(), settings.clone())
.await
.unwrap();

let result2 = request.execute(&dapi, settings).await.unwrap();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl<S: Signer> PurchaseDocument<S> for Document {
purchaser_id,
document_type.data_contract_id(),
true,
settings,
settings.clone(),
)
.await?;

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/src/platform/transition/put_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl<S: Signer> PutContract<S> for DataContract {
settings: Option<PutSettings>,
) -> Result<StateTransition, Error> {
let new_identity_nonce = sdk
.get_identity_nonce(self.owner_id(), true, settings)
.get_identity_nonce(self.owner_id(), true, settings.clone())
.await?;

let key_id = identity_public_key.id();
Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/src/platform/transition/put_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl<S: Signer> PutDocument<S> for Document {
self.owner_id(),
document_type.data_contract_id(),
true,
settings,
settings.clone(),
)
.await?;

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/src/platform/transition/put_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use dpp::prelude::UserFeeIncrease;
use rs_dapi_client::RequestSettings;

/// The options when putting something to platform
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Default)]
pub struct PutSettings {
pub request_settings: RequestSettings,
pub identity_nonce_stale_time_s: Option<u64>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl<S: Signer> TransferDocument<S> for Document {
self.owner_id(),
document_type.data_contract_id(),
true,
settings,
settings.clone(),
)
.await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl<S: Signer> UpdatePriceOfDocument<S> for Document {
self.owner_id(),
document_type.data_contract_id(),
true,
settings,
settings.clone(),
)
.await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ impl WithdrawFromIdentity for Identity {
signer: S,
settings: Option<PutSettings>,
) -> Result<u64, Error> {
let new_identity_nonce = sdk.get_identity_nonce(self.id(), true, settings).await?;
let new_identity_nonce = sdk
.get_identity_nonce(self.id(), true, settings.clone())
.await?;
let state_transition = IdentityCreditWithdrawalTransition::try_from_identity(
self,
None,
Expand Down
1 change: 1 addition & 0 deletions packages/rs-sdk/tests/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
DASH_SDK_PLATFORM_HOST="127.0.0.1"
DASH_SDK_PLATFORM_PORT=2443
DASH_SDK_PLATFORM_SSL=false
# DASH_SDK_PLATFORM_CA_CERT_PATH=/some/path/to/ca.pem

# ProTxHash of masternode that has at least 1 vote casted for DPNS name `testname`
DASH_SDK_MASTERNODE_OWNER_PRO_REG_TX_HASH="6ac88f64622d9bc0cb79ad0f69657aa9488b213157d20ae0ca371fa5f04fb222"
Expand Down
30 changes: 24 additions & 6 deletions packages/rs-sdk/tests/fetch/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! This module contains [Config] struct that can be used to configure dash-platform-sdk.
//! It's mainly used for testing.

use dash_sdk::RequestSettings;
use dpp::platform_value::string_encoding::Encoding;
use dpp::{
dashcore::{hashes::Hash, ProTxHash},
Expand Down Expand Up @@ -48,6 +49,10 @@ pub struct Config {
#[serde(default)]
pub platform_ssl: bool,

/// When platform_ssl is true, use the PEM-encoded CA certificate from provided absolute path to verify the server
#[serde(default)]
pub platform_ca_cert_path: Option<PathBuf>,

/// Directory where all generated test vectors will be saved.
///
/// See [SdkBuilder::with_dump_dir()](crate::SdkBuilder::with_dump_dir()) for more details.
Expand Down Expand Up @@ -172,16 +177,28 @@ impl Config {
panic!("cannot use namespace with root dump dir");
}

let request_settings = self
.platform_ca_cert_path
.as_ref()
.map(|cert| {
RequestSettings::default()
.with_ca_certificate(cert)
.expect("failed to load CA certificate")
})
.unwrap_or_default();

// offline testing takes precedence over network testing
#[cfg(all(feature = "network-testing", not(feature = "offline-testing")))]
let sdk = {
// Dump all traffic to disk
let builder = dash_sdk::SdkBuilder::new(self.address_list()).with_core(
&self.platform_host,
self.core_port,
&self.core_user,
&self.core_password,
);
let builder = dash_sdk::SdkBuilder::new(self.address_list())
.with_core(
&self.platform_host,
self.core_port,
&self.core_user,
&self.core_password,
)
.with_settings(request_settings);

#[cfg(feature = "generate-test-vectors")]
let builder = {
Expand Down Expand Up @@ -209,6 +226,7 @@ impl Config {
#[cfg(feature = "offline-testing")]
let sdk = {
let mut mock_sdk = dash_sdk::SdkBuilder::new_mock()
.with_settings(request_settings)
.build()
.expect("initialize api");

Expand Down

0 comments on commit 431ec00

Please sign in to comment.