From 10170a9fe7f1468727be4a6547c00c0e280d09ea Mon Sep 17 00:00:00 2001 From: leon3s Date: Wed, 31 Jan 2024 18:07:42 +0100 Subject: [PATCH] feature: client ssl --- bin/nanocl/Cargo.toml | 2 +- bin/nanocl/src/commands/install.rs | 1 + bin/nanocl/src/main.rs | 25 ++++++----- bin/nanocl/src/models/context.rs | 5 +++ crates/nanocl_stubs/src/system.rs | 16 ++++++- crates/nanocld_client/src/http_client.rs | 54 +++++++++++++----------- examples/nanocl_context_ssl.yml | 9 ++++ 7 files changed, 72 insertions(+), 40 deletions(-) create mode 100644 examples/nanocl_context_ssl.yml diff --git a/bin/nanocl/Cargo.toml b/bin/nanocl/Cargo.toml index db9d12ed6..f5f2fac58 100644 --- a/bin/nanocl/Cargo.toml +++ b/bin/nanocl/Cargo.toml @@ -56,7 +56,7 @@ dialoguer = "0.11" termios = "0.3" liquid = { version = "0.26", features = ["stdlib"] } regex = "1.10" -nanocld_client = { version = "0.13", features = ["tokio"] } +nanocld_client = { version = "0.13", features = ["tokio", "openssl"] } nanocl_error = { version = "0.2", features = [ "io", "tokio", diff --git a/bin/nanocl/src/commands/install.rs b/bin/nanocl/src/commands/install.rs index e6195d1d6..ecd1d2973 100644 --- a/bin/nanocl/src/commands/install.rs +++ b/bin/nanocl/src/commands/install.rs @@ -169,6 +169,7 @@ pub async fn exec_install(args: &InstallOpts) -> IoResult<()> { "Nanocl".into(), ContextEndpoint { host: format!("unix://{home_dir}/.nanocl/run/nanocl.sock"), + ssl: None, }, ); map diff --git a/bin/nanocl/src/main.rs b/bin/nanocl/src/main.rs index 897d923b5..d1a94b498 100644 --- a/bin/nanocl/src/main.rs +++ b/bin/nanocl/src/main.rs @@ -28,22 +28,21 @@ fn create_cli_config(cli_args: &Cli) -> IoResult { } } } + let endpoint = context.endpoints.get("Nanocl").unwrap(); #[allow(unused)] - let mut host = cli_args - .host - .clone() - .unwrap_or(context.endpoints.get("Nanocl").unwrap().host.clone()); - #[cfg(any(feature = "dev", feature = "test"))] - { - if context.name == "default" { - host = cli_args - .host - .clone() - .unwrap_or("http://nanocl.internal:8585".into()); - } - } + let mut host = cli_args.host.clone().unwrap_or(endpoint.host.clone()); + // #[cfg(any(feature = "dev", feature = "test"))] + // { + // if context.name == "default" { + // host = cli_args + // .host + // .clone() + // .unwrap_or("http://nanocl.internal:8585".into()); + // } + // } let client = NanocldClient::connect_to(&ConnectOpts { url: host.clone(), + ssl: endpoint.ssl.clone(), ..Default::default() }); Ok(CliConfig { diff --git a/bin/nanocl/src/models/context.rs b/bin/nanocl/src/models/context.rs index 7d4f970ce..152175dd9 100644 --- a/bin/nanocl/src/models/context.rs +++ b/bin/nanocl/src/models/context.rs @@ -4,6 +4,8 @@ use tabled::Tabled; use clap::{Parser, Subcommand}; use serde::{Serialize, Deserialize}; +use nanocld_client::stubs::system::SslConfig; + /// `nanocl context` available arguments #[derive(Parser)] pub struct ContextArg { @@ -34,6 +36,8 @@ pub enum ContextCommand { #[serde(rename_all = "PascalCase")] pub struct ContextEndpoint { pub host: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssl: Option, } /// A context metadata definition @@ -67,6 +71,7 @@ impl std::default::Default for Context { ContextEndpoint { host: std::env::var("NANOCL_HOST") .unwrap_or("unix:///run/nanocl/nanocl.sock".into()), + ssl: None, }, ); map diff --git a/crates/nanocl_stubs/src/system.rs b/crates/nanocl_stubs/src/system.rs index 2c06d8f75..415e601fe 100644 --- a/crates/nanocl_stubs/src/system.rs +++ b/crates/nanocl_stubs/src/system.rs @@ -7,17 +7,29 @@ use serde::{Serialize, Deserialize}; use crate::config::DaemonConfig; -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default)] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] +#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))] #[cfg_attr(feature = "clap", derive(clap::Parser))] pub struct SslConfig { + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] #[cfg_attr(feature = "clap", clap(long))] pub cert: Option, + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] #[cfg_attr(feature = "clap", clap(long))] pub cert_key: Option, + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] #[cfg_attr(feature = "clap", clap(long))] pub cert_ca: Option, } diff --git a/crates/nanocld_client/src/http_client.rs b/crates/nanocld_client/src/http_client.rs index 25ab7280b..0ce401364 100644 --- a/crates/nanocld_client/src/http_client.rs +++ b/crates/nanocld_client/src/http_client.rs @@ -1,5 +1,6 @@ use std::error::Error; +use nanocl_stubs::system::SslConfig; use ntex::rt; use ntex::http; @@ -10,7 +11,6 @@ use futures::{StreamExt, TryStreamExt}; use nanocl_error::io::FromIo; use nanocl_error::http::HttpError; use nanocl_error::http_client::HttpClientError; - use crate::error::is_api_error; pub const NANOCLD_DEFAULT_VERSION: &str = "0.13.0"; @@ -22,11 +22,7 @@ pub struct ConnectOpts { /// Optional version pub version: Option, /// Optional certificate path - pub cert: Option, - /// Optional certificate key path - pub cert_key: Option, - /// Optional ca certificate path - pub cert_ca: Option, + pub ssl: Option, } #[derive(Clone)] @@ -34,9 +30,7 @@ pub struct NanocldClient { pub url: String, pub version: String, pub unix_socket: Option, - pub cert: Option, - pub cert_key: Option, - pub cert_ca: Option, + pub ssl: Option, } impl Default for ConnectOpts { @@ -44,9 +38,7 @@ impl Default for ConnectOpts { Self { url: String::from("unix:///run/nanocl/nanocl.sock"), version: None, - cert: None, - cert_key: None, - cert_ca: None, + ssl: None, } } } @@ -63,9 +55,7 @@ impl NanocldClient { unix_socket: Some(String::from("/run/nanocl/nanocl.sock")), version: format!("v{NANOCLD_DEFAULT_VERSION}"), url: "http://localhost".to_owned(), - cert: None, - cert_key: None, - cert_ca: None, + ssl: None, } } @@ -78,9 +68,7 @@ impl NanocldClient { url: url.to_owned(), unix_socket: None, version: version.unwrap_or(format!("v{NANOCLD_DEFAULT_VERSION}")), - cert: opts.cert.clone(), - cert_key: opts.cert_key.clone(), - cert_ca: opts.cert_ca.clone(), + ssl: opts.ssl.clone(), } } url if url.starts_with("unix://") => { @@ -89,9 +77,7 @@ impl NanocldClient { url: "http://localhost".to_owned(), unix_socket: Some(path.to_owned()), version: version.unwrap_or(format!("v{NANOCLD_DEFAULT_VERSION}")), - cert: None, - cert_key: None, - cert_ca: None, + ssl: None, } } _ => panic!("Invalid url: {}", url), @@ -107,9 +93,7 @@ impl NanocldClient { unix_socket: Some(String::from("/run/nanocl/nanocl.sock")), version: version.to_owned(), url: String::from("http://localhost"), - cert: None, - cert_key: None, - cert_ca: None, + ssl: None, } } @@ -127,6 +111,28 @@ impl NanocldClient { .finish(), ); } + #[cfg(feature = "openssl")] + { + use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, SslFiletype}; + if let Some(ssl) = &self.ssl { + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + builder + .set_certificate_file(&ssl.cert.clone().unwrap(), SslFiletype::PEM) + .unwrap(); + builder + .set_private_key_file( + &ssl.cert_key.clone().unwrap(), + SslFiletype::PEM, + ) + .unwrap(); + client = ntex::http::client::Client::build().connector( + http::client::Connector::default() + .openssl(builder.build()) + .finish(), + ) + } + } client.timeout(ntex::time::Millis::from_secs(100)).finish() } diff --git a/examples/nanocl_context_ssl.yml b/examples/nanocl_context_ssl.yml new file mode 100644 index 000000000..0e4966f54 --- /dev/null +++ b/examples/nanocl_context_ssl.yml @@ -0,0 +1,9 @@ +Name: ssl-conn +MetaData: + Description: Connection secure with ssl +Endpoints: + Nanocl: + Host: https://nanocl.internal:9443 + Ssl: + Cert: ./tests/client.crt + CertKey: ./tests/client.key