1use std::sync::Arc;
2use std::thread::available_parallelism;
3use std::time::Duration;
4use std::{fmt::Display, str::FromStr};
5
6use log::info;
7use nethsm_sdk_rs::ureq::{
8 Agent,
9 tls::{Certificate, RootCerts, TlsConfig, TlsProvider},
10};
11use serde::{Deserialize, Serialize};
12
13use crate::Error;
14#[cfg(doc)]
15use crate::NetHsm;
16
17pub const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
19
20pub const DEFAULT_TIMEOUT_SECONDS: u64 = 10;
22
23#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
25pub struct RootCertificates(Vec<Vec<u8>>);
26
27impl From<&RootCertificates> for RootCerts {
28 fn from(value: &RootCertificates) -> Self {
29 let certs = value
30 .0
31 .iter()
32 .map(|cert| Certificate::from_der(cert).to_owned())
33 .collect::<Vec<_>>();
34 RootCerts::Specific(Arc::new(certs))
35 }
36}
37
38#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
40pub enum ConnectionSecurity {
41 Unsafe,
43 Native,
45 RootCertificates(RootCertificates),
47}
48
49impl Display for ConnectionSecurity {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 Self::Unsafe => write!(f, "unsafe"),
53 Self::Native => write!(f, "native"),
54 Self::RootCertificates(_) => write!(f, "custom root certificates"),
55 }
56 }
57}
58
59impl FromStr for ConnectionSecurity {
60 type Err = Error;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 match s {
85 "unsafe" | "Unsafe" => Ok(Self::Unsafe),
86 "native" | "Native" => Ok(Self::Native),
87 _ => Err(Error::Default(format!("Invalid connection security: {s}"))),
88 }
89 }
90}
91
92pub(crate) fn create_agent(
109 tls_security: ConnectionSecurity,
110 max_idle_connections: Option<usize>,
111 timeout_seconds: Option<u64>,
112) -> Result<Agent, Error> {
113 let max_idle_connections = max_idle_connections
114 .or_else(|| available_parallelism().ok().map(Into::into))
115 .unwrap_or(DEFAULT_MAX_IDLE_CONNECTIONS);
116 let timeout_seconds = timeout_seconds.unwrap_or(DEFAULT_TIMEOUT_SECONDS);
117 info!(
118 "NetHSM connection configured with \"max_idle_connection\" {max_idle_connections} and \"timeout_seconds\" {timeout_seconds}."
119 );
120 let tls_config = {
121 let mut tls_config_builder = TlsConfig::builder().provider(TlsProvider::Rustls);
122
123 tls_config_builder = match &tls_security {
124 ConnectionSecurity::Unsafe => tls_config_builder.disable_verification(true),
125 ConnectionSecurity::Native => {
126 tls_config_builder.root_certs(RootCerts::PlatformVerifier)
127 }
128 ConnectionSecurity::RootCertificates(root_certs) => {
129 tls_config_builder.root_certs(RootCerts::from(root_certs))
130 }
131 };
132
133 tls_config_builder.build()
134 };
135 let agent = Agent::config_builder()
136 .max_idle_connections(max_idle_connections)
137 .max_idle_connections_per_host(max_idle_connections)
138 .timeout_connect(Some(Duration::from_secs(timeout_seconds)))
139 .tls_config(tls_config)
140 .build()
141 .new_agent();
142
143 Ok(agent)
144}
145
146#[cfg(test)]
147mod tests {
148 use rstest::rstest;
149 use testresult::TestResult;
150
151 use super::*;
152
153 #[rstest]
154 #[case(ConnectionSecurity::Native, "native")]
155 #[case(ConnectionSecurity::Unsafe, "unsafe")]
156 #[case(ConnectionSecurity::RootCertificates(RootCertificates(vec![vec![]])), "custom root certificates")]
157 fn connectionsecurity_display(
158 #[case] connection_security: ConnectionSecurity,
159 #[case] expected: &str,
160 ) -> TestResult {
161 assert_eq!(connection_security.to_string(), expected);
162 Ok(())
163 }
164}