diff --git a/control-plane/csi-driver/src/bin/controller/client.rs b/control-plane/csi-driver/src/bin/controller/client.rs index 5794505cc..be9ea9f83 100644 --- a/control-plane/csi-driver/src/bin/controller/client.rs +++ b/control-plane/csi-driver/src/bin/controller/client.rs @@ -123,16 +123,49 @@ impl RestApiClient { let url = clients::tower::Url::parse(endpoint) .map_err(|error| anyhow!("Invalid API endpoint URL {}: {:?}", endpoint, error))?; let concurrency_limit = cfg.create_volume_limit() * 2; - let tower = clients::tower::Configuration::builder() - .with_timeout(cfg.io_timeout()) - .with_concurrency_limit(Some(concurrency_limit)) - .build_url(url) - .map_err(|error| { - anyhow::anyhow!( - "Failed to create openapi configuration, Error: '{:?}'", - error - ) - })?; + let ca_certificate_path = cfg.ca_certificate_path(); + let cert = match ca_certificate_path { + Some(path) => { + let cert = std::fs::read(path).map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration at path {}, Error: '{:?}'", + path.display(), + error + ) + })?; + Some(cert) + } + None => None, + }; + let tower = match (url.scheme(), cert) { + ("https", Some(cert)) => clients::tower::Configuration::builder() + .with_timeout(Some(cfg.io_timeout())) + .with_concurrency_limit(Some(concurrency_limit)) + .with_certificate(cert.as_slice()) + .build_url(url) + .map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration, Error: '{:?}'", + error + ) + })?, + ("https", None) => { + anyhow::bail!("HTTPS endpoint requires a CA certificate path"); + } + (_, Some(_path)) => { + anyhow::bail!("CA certificate path is only supported for HTTPS endpoints"); + } + _ => clients::tower::Configuration::builder() + .with_timeout(Some(cfg.io_timeout())) + .with_concurrency_limit(Some(concurrency_limit)) + .build_url(url) + .map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration, Error: '{:?}'", + error + ) + })?, + }; REST_CLIENT.get_or_init(|| Self { rest_client: clients::tower::ApiClient::new(tower.clone()), diff --git a/control-plane/csi-driver/src/bin/controller/config.rs b/control-plane/csi-driver/src/bin/controller/config.rs index b06d380ce..472fa6aac 100755 --- a/control-plane/csi-driver/src/bin/controller/config.rs +++ b/control-plane/csi-driver/src/bin/controller/config.rs @@ -1,7 +1,11 @@ use anyhow::Context; use clap::ArgMatches; use once_cell::sync::OnceCell; -use std::{collections::HashMap, time::Duration}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + time::Duration, +}; static CONFIG: OnceCell = OnceCell::new(); @@ -17,6 +21,8 @@ pub(crate) struct CsiControllerConfig { create_volume_limit: usize, /// Force unstage volume. force_unstage_volume: bool, + /// Path to the CA certificate file. + ca_certificate_path: Option, } impl CsiControllerConfig { @@ -50,7 +56,9 @@ impl CsiControllerConfig { tracing::warn!( "Force unstage volume is disabled, can trigger potential data corruption!" ); - } + }; + + let ca_certificate_path: Option<&PathBuf> = args.get_one::("tls-client-ca-path"); CONFIG.get_or_init(|| Self { rest_endpoint: rest_endpoint.into(), @@ -58,6 +66,7 @@ impl CsiControllerConfig { node_selector, create_volume_limit, force_unstage_volume, + ca_certificate_path: ca_certificate_path.cloned(), }); Ok(()) } @@ -92,4 +101,9 @@ impl CsiControllerConfig { pub(crate) fn force_unstage_volume(&self) -> bool { self.force_unstage_volume } + + /// Path to the CA certificate file. + pub(crate) fn ca_certificate_path(&self) -> Option<&Path> { + self.ca_certificate_path.as_deref() + } } diff --git a/control-plane/csi-driver/src/bin/controller/main.rs b/control-plane/csi-driver/src/bin/controller/main.rs index a70e4f7ad..38d5650a2 100644 --- a/control-plane/csi-driver/src/bin/controller/main.rs +++ b/control-plane/csi-driver/src/bin/controller/main.rs @@ -131,6 +131,11 @@ async fn main() -> anyhow::Result<()> { .value_parser(clap::value_parser!(bool)) .help("Enable force unstage volume feature") ) + .arg( + Arg::new("tls-client-ca-path") + .long("tls-client-ca-path") + .help("path to the CA certificate file") + ) .get_matches(); utils::print_package_info!(); diff --git a/control-plane/csi-driver/src/bin/node/client.rs b/control-plane/csi-driver/src/bin/node/client.rs index 77bddc74d..a33ffc4ad 100644 --- a/control-plane/csi-driver/src/bin/node/client.rs +++ b/control-plane/csi-driver/src/bin/node/client.rs @@ -6,7 +6,7 @@ use stor_port::types::v0::openapi::{ }; use anyhow::anyhow; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use stor_port::types::v0::openapi::{ apis::{ app_nodes_api::tower::client::direct::AppNodes, @@ -107,6 +107,7 @@ impl AppNodesClientWrapper { /// Initialize AppNodes API client instance. pub(crate) fn initialize( endpoint: Option<&String>, + ca_certificate_path: Option<&PathBuf>, ) -> anyhow::Result> { const REST_TIMEOUT: Duration = Duration::from_secs(5); @@ -117,12 +118,49 @@ impl AppNodesClientWrapper { let url = clients::tower::Url::parse(endpoint) .map_err(|error| anyhow!("Invalid API endpoint URL {endpoint}: {error:?}"))?; - let tower = clients::tower::Configuration::builder() - .with_timeout(REST_TIMEOUT) - .build_url(url) - .map_err(|error| { - anyhow::anyhow!("Failed to create openapi configuration, Error: '{error:?}'") - })?; + let cert = match ca_certificate_path { + Some(path) => { + let cert = std::fs::read(path).map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration at path {}, Error: '{:?}'", + path.display(), + error + ) + })?; + Some(cert) + } + None => None, + }; + + let tower = match (url.scheme(), cert) { + ("https", Some(cert)) => clients::tower::Configuration::builder() + .with_timeout(REST_TIMEOUT) + .with_concurrency_limit(Some(10)) + .with_certificate(&cert) + .build_url(url) + .map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration***, Error: '{:?}'", + error + ) + })?, + ("https", None) => { + anyhow::bail!("HTTPS endpoint requires a CA certificate path"); + } + (_, Some(_path)) => { + anyhow::bail!("CA certificate path is only supported for HTTPS endpoints"); + } + _ => clients::tower::Configuration::builder() + .with_timeout(REST_TIMEOUT) + .with_concurrency_limit(Some(10)) + .build_url(url) + .map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration, Error???: '{:?}'", + error + ) + })?, + }; info!( "API client is initialized with endpoint {endpoint}, request timeout = {REST_TIMEOUT:?}" @@ -169,20 +207,56 @@ pub(crate) struct VolumesClientWrapper { impl VolumesClientWrapper { /// Initialize VolumesClientWrapper instance. - pub(crate) fn new(endpoint: &str) -> anyhow::Result { + pub(crate) fn new( + endpoint: &str, + ca_certificate_path: Option, + ) -> anyhow::Result { /// TODO: what's the NodeStage timeout? const REST_TIMEOUT: Duration = Duration::from_secs(10); let url = clients::tower::Url::parse(endpoint) .map_err(|error| anyhow!("Invalid API endpoint URL {endpoint}: {error:?}"))?; + let cert = match ca_certificate_path { + Some(path) => { + let cert = std::fs::read(path.clone()).map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration at path {}, Error: '{:?}'", + path.display(), + error + ) + })?; + Some(cert) + } + None => None, + }; - let config = clients::tower::Configuration::builder() - .with_timeout(REST_TIMEOUT) - .with_concurrency_limit(Some(10)) - .build_url(url) - .map_err(|error| { - anyhow::anyhow!("Failed to create openapi configuration, Error: '{error:?}'") - })?; + let config = match (url.scheme(), cert) { + ("https", Some(cert)) => clients::tower::Configuration::builder() + .with_timeout(REST_TIMEOUT) + .with_certificate(&cert) + .build_url(url) + .map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration***, Error: '{:?}'", + error + ) + })?, + ("https", None) => { + anyhow::bail!("HTTPS endpoint requires a CA certificate path"); + } + (_, Some(_path)) => { + anyhow::bail!("CA certificate path is only supported for HTTPS endpoints"); + } + _ => clients::tower::Configuration::builder() + .with_timeout(REST_TIMEOUT) + .build_url(url) + .map_err(|error| { + anyhow::anyhow!( + "Failed to create openapi configuration, Error???: '{:?}'", + error + ) + })?, + }; info!( "VolumesClient API is initialized with endpoint {endpoint}, request timeout = {REST_TIMEOUT:?}" diff --git a/control-plane/csi-driver/src/bin/node/main_.rs b/control-plane/csi-driver/src/bin/node/main_.rs index 4858a40e6..34702f2f0 100644 --- a/control-plane/csi-driver/src/bin/node/main_.rs +++ b/control-plane/csi-driver/src/bin/node/main_.rs @@ -29,6 +29,7 @@ use std::{ future::Future, io::ErrorKind, net::{IpAddr, SocketAddr}, + path::PathBuf, str::FromStr, }; use tokio::net::UnixListener; @@ -191,6 +192,13 @@ pub(super) async fn main() -> anyhow::Result<()> { .default_value("/var/lib/kubelet") .help("Kubelet path on the host system") ) + .arg( + Arg::new("tls-client-ca-path") + .long("tls-client-ca-path") + .value_parser(clap::value_parser!(PathBuf)) + .requires("enable-rest") + .help("path to the CA certificate file") + ) .subcommand( clap::Command::new("fs-freeze") .arg( @@ -345,7 +353,10 @@ pub(super) async fn main() -> anyhow::Result<()> { } // Initialize the rest api client. - let client = AppNodesClientWrapper::initialize(matches.get_one::("rest-endpoint"))?; + let client = AppNodesClientWrapper::initialize( + matches.get_one::("rest-endpoint"), + matches.get_one::("tls-client-ca-path"), + )?; let registration_enabled = matches.get_flag("enable-registration"); @@ -403,7 +414,10 @@ impl CsiServer { }; let vol_client = match cli_args.get_one::("rest-endpoint") { - Some(ep) if cli_args.get_flag("enable-rest") => Some(VolumesClientWrapper::new(ep)?), + Some(ep) if cli_args.get_flag("enable-rest") => { + let cert = cli_args.get_one::("tls-client-ca-path"); + Some(VolumesClientWrapper::new(ep, cert.cloned())?) + } _ => { tracing::warn!("The rest client is not enabled - functionality may be limited"); None diff --git a/control-plane/rest/service/src/main.rs b/control-plane/rest/service/src/main.rs index 7f2be800f..8b7b2611d 100644 --- a/control-plane/rest/service/src/main.rs +++ b/control-plane/rest/service/src/main.rs @@ -185,6 +185,7 @@ fn load_certificates( .map_err(|_| { anyhow::anyhow!("Failed to retrieve the rsa private keys from the key file",) })?; + if keys.is_empty() { anyhow::bail!("No keys found in the keys file"); } diff --git a/k8s/operators/src/pool/main.rs b/k8s/operators/src/pool/main.rs index c47512033..e845991c3 100644 --- a/k8s/operators/src/pool/main.rs +++ b/k8s/operators/src/pool/main.rs @@ -129,14 +129,49 @@ async fn pool_controller(args: ArgMatches) -> anyhow::Result<()> { .expect("timeout value is invalid") .into(); - let cfg = clients::tower::Configuration::new(url, timeout, None, None, true, None).map_err( - |error| { + let ca_certificate_path: Option<&str> = args + .get_one::("tls-client-ca-path") + .map(|x| x.as_str()); + // take in cert path and make pem file + let cert = match ca_certificate_path { + Some(path) => { + let cert = std::fs::read(path).map_err(|error| { + anyhow::anyhow!("Failed to read certificate file, Error: '{:?}'", error) + })?; + Some(cert) + } + None => None, + }; + let cfg = match (url.scheme(), cert) { + ("https", Some(cert)) => clients::tower::Configuration::new( + url, + timeout, + None, + Some(cert.as_slice()), + true, + None, + ) + .map_err(|error| { anyhow::anyhow!( "Failed to create openapi configuration, Error: '{:?}'", error ) - }, - )?; + })?, + ("https", None) => { + anyhow::bail!("HTTPS endpoint requires a CA certificate path"); + } + (_, Some(_path)) => { + anyhow::bail!("CA certificate path is only supported for HTTPS endpoints"); + } + _ => clients::tower::Configuration::new(url, timeout, None, None, true, None).map_err( + |error| { + anyhow::anyhow!( + "Failed to create openapi configuration, Error: '{:?}'", + error + ) + }, + )?, + }; let interval = args .get_one::("interval") .unwrap() @@ -243,6 +278,11 @@ async fn main() -> anyhow::Result<()> { .value_parser(clap::value_parser!(bool)) .help("Enable ansi color for logs"), ) + .arg( + Arg::new("tls-client-ca-path") + .long("tls-client-ca-path") + .help("path to the CA certificate file"), + ) .get_matches(); utils::print_package_info!();