Skip to main content

nethsm/base/
impl_base.rs

1//! Base implementation for [`NetHsm`]
2
3#[cfg(doc)]
4use std::thread::available_parallelism;
5use std::{cell::RefCell, collections::HashMap};
6
7use log::{debug, trace};
8use nethsm_sdk_rs::apis::configuration::Configuration;
9
10use crate::{
11    Connection,
12    ConnectionSecurity,
13    Credentials,
14    DEFAULT_MAX_IDLE_CONNECTIONS,
15    DEFAULT_TIMEOUT_SECONDS,
16    Error,
17    NetHsm,
18    Url,
19    UserId,
20    UserRole,
21    tls::create_agent,
22    user::NamespaceSupport,
23};
24
25impl NetHsm {
26    /// Creates a new NetHSM connection.
27    ///
28    /// Creates a new NetHSM connection based on a [`Connection`].
29    ///
30    /// Optionally initial `credentials` (used when communicating with the NetHSM),
31    /// `max_idle_connections` to set the size of the connection pool (defaults to `100`) and
32    /// `timeout_seconds` to set the timeout for a successful socket connection (defaults to `10`)
33    /// can be provided.
34    ///
35    /// # Errors
36    ///
37    /// - the TLS client configuration can not be created,
38    /// - or [`ConnectionSecurity::Native`] is provided as `tls_security`, but no certification
39    ///   authority certificates are available on the system.
40    pub fn new(
41        connection: Connection,
42        credentials: Option<Credentials>,
43        max_idle_connections: Option<usize>,
44        timeout_seconds: Option<u64>,
45    ) -> Result<Self, Error> {
46        let (current_credentials, credentials) = if let Some(credentials) = credentials {
47            debug!(
48                "Create new NetHSM connection {connection} with initial credentials {credentials}"
49            );
50            (
51                RefCell::new(Some(credentials.user_id.clone())),
52                RefCell::new(HashMap::from([(credentials.user_id.clone(), credentials)])),
53            )
54        } else {
55            debug!("Create new NetHSM connection {connection} with no initial credentials");
56            (Default::default(), Default::default())
57        };
58
59        let agent = RefCell::new(create_agent(
60            connection.tls_security,
61            max_idle_connections,
62            timeout_seconds,
63        )?);
64
65        Ok(Self {
66            agent,
67            url: RefCell::new(connection.url),
68            current_credentials,
69            credentials,
70        })
71    }
72
73    /// Validates the potential [namespace] access of a context.
74    ///
75    /// Validates, that [`current_credentials`][`NetHsm::current_credentials`] can be used in a
76    /// defined context. This function relies on [`UserId::validate_namespace_access`] and should be
77    /// used for validating the context of [`NetHsm`] methods.
78    ///
79    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
80    pub(crate) fn validate_namespace_access(
81        &self,
82        support: NamespaceSupport,
83        target: Option<&UserId>,
84        role: Option<&UserRole>,
85    ) -> Result<(), Error> {
86        debug!(
87            "Validate namespace access (target: {}; namespace: {support}; role: {}) for NetHSM at {}",
88            if let Some(target) = target {
89                target.to_string()
90            } else {
91                "n/a".to_string()
92            },
93            if let Some(role) = role {
94                role.to_string()
95            } else {
96                "n/a".to_string()
97            },
98            self.url.borrow()
99        );
100
101        if let Some(current_user_id) = self.current_credentials.borrow().to_owned() {
102            current_user_id.validate_namespace_access(support, target, role)?
103        }
104        Ok(())
105    }
106
107    /// Creates a connection configuration.
108    ///
109    /// Uses the [`Agent`][`nethsm_sdk_rs::ureq::Agent`] configured during creation of the
110    /// [`NetHsm`], the current [`Url`] and [`Credentials`] to create a [`Configuration`] for a
111    /// connection to the API of a NetHSM.
112    pub(crate) fn create_connection_config(&self) -> Configuration {
113        debug!(
114            "Create connection config for NetHSM at {}",
115            self.url.borrow()
116        );
117
118        let current_credentials = self.current_credentials.borrow().to_owned();
119        Configuration {
120            client: self.agent.borrow().to_owned(),
121            base_path: self.url.borrow().to_string(),
122            basic_auth: if let Some(current_credentials) = current_credentials {
123                self.credentials
124                    .borrow()
125                    .get(&current_credentials)
126                    .map(Into::into)
127            } else {
128                None
129            },
130            user_agent: Some(format!(
131                "{}/{}",
132                env!("CARGO_PKG_NAME"),
133                env!("CARGO_PKG_VERSION")
134            )),
135            ..Default::default()
136        }
137    }
138
139    /// Sets the connection agent for the NetHSM connection.
140    ///
141    /// Allows setting the
142    /// - [`ConnectionSecurity`] which defines the TLS security model for the connection,
143    /// - maximum idle connections per host using the optional `max_idle_connections` (defaults to
144    ///   [`available_parallelism`] and falls back to `100` if unavailable),
145    /// - and timeout in seconds for a successful socket connection using the optional
146    ///   `timeout_seconds` (defaults to `10`).
147    ///
148    /// # Errors
149    ///
150    /// Returns an error if
151    ///
152    /// - the TLS client configuration can not be created,
153    /// - [`ConnectionSecurity::Native`] is provided as `tls_security`, but no certification
154    ///   authority certificates are available on the system.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use nethsm::{Connection, ConnectionSecurity, NetHsm, Url};
160    ///
161    /// # fn main() -> testresult::TestResult {
162    /// // Create a new connection for a NetHSM at "https://example.org"
163    /// let nethsm = NetHsm::new(
164    ///     Connection::new(
165    ///         "https://example.org/api/v1".try_into()?,
166    ///         ConnectionSecurity::Unsafe,
167    ///     ),
168    ///     None,
169    ///     None,
170    ///     None,
171    /// )?;
172    ///
173    /// // change the connection agent to something else
174    /// nethsm.set_agent(ConnectionSecurity::Unsafe, Some(200), Some(30))?;
175    /// # Ok(())
176    /// # }
177    /// ```
178    pub fn set_agent(
179        &self,
180        tls_security: ConnectionSecurity,
181        max_idle_connections: Option<usize>,
182        timeout_seconds: Option<u64>,
183    ) -> Result<(), Error> {
184        debug!(
185            "Set TLS agent (TLS security: {tls_security}; max idle: {}, timeout: {}) for NetHSM at {}",
186            if let Some(max_idle_connections) = max_idle_connections {
187                max_idle_connections.to_string()
188            } else {
189                DEFAULT_MAX_IDLE_CONNECTIONS.to_string()
190            },
191            if let Some(timeout_seconds) = timeout_seconds {
192                format!("{timeout_seconds}s")
193            } else {
194                format!("{DEFAULT_TIMEOUT_SECONDS}s")
195            },
196            self.url.borrow()
197        );
198
199        *self.agent.borrow_mut() =
200            create_agent(tls_security, max_idle_connections, timeout_seconds)?;
201        Ok(())
202    }
203
204    /// Sets the URL for the NetHSM connection.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use nethsm::{Connection, ConnectionSecurity, NetHsm, Url};
210    ///
211    /// # fn main() -> testresult::TestResult {
212    /// // Create a new connection for a NetHSM at "https://example.org"
213    /// let nethsm = NetHsm::new(
214    ///     Connection::new(
215    ///         "https://example.org/api/v1".try_into()?,
216    ///         ConnectionSecurity::Unsafe,
217    ///     ),
218    ///     None,
219    ///     None,
220    ///     None,
221    /// )?;
222    ///
223    /// // change the url to something else
224    /// nethsm.set_url(Url::new("https://other.org/api/v1")?);
225    /// # Ok(())
226    /// # }
227    /// ```
228    pub fn set_url(&self, url: Url) {
229        debug!(
230            "Set the URL to {url} for the NetHSM at {}",
231            self.url.borrow()
232        );
233
234        *self.url.borrow_mut() = url;
235    }
236
237    /// Retrieves the current URL for the NetHSM connection.
238    ///
239    /// # Examples
240    ///
241    /// ```
242    /// use nethsm::{Connection, ConnectionSecurity, NetHsm, Url};
243    ///
244    /// # fn main() -> testresult::TestResult {
245    /// // Create a new connection for a NetHSM at "https://example.org"
246    /// let nethsm = NetHsm::new(
247    ///     Connection::new(
248    ///         "https://example.org/api/v1".try_into()?,
249    ///         ConnectionSecurity::Unsafe,
250    ///     ),
251    ///     None,
252    ///     None,
253    ///     None,
254    /// )?;
255    ///
256    /// // retrieve the current URL
257    /// assert_eq!(nethsm.get_url(), "https://example.org/api/v1".try_into()?);
258    /// # Ok(())
259    /// # }
260    /// ```
261    pub fn get_url(&self) -> Url {
262        trace!("Get the URL for the NetHSM at {}", self.url.borrow());
263
264        self.url.borrow().clone()
265    }
266
267    /// Adds [`Credentials`] to the list of available ones.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm, Passphrase};
273    ///
274    /// # fn main() -> testresult::TestResult {
275    /// let nethsm = NetHsm::new(
276    ///     Connection::new(
277    ///         "https://example.org/api/v1".try_into()?,
278    ///         ConnectionSecurity::Unsafe,
279    ///     ),
280    ///     None,
281    ///     None,
282    ///     None,
283    /// )?;
284    ///
285    /// // add credentials
286    /// nethsm.add_credentials(Credentials::new(
287    ///     "admin".parse()?,
288    ///     Some(Passphrase::new("passphrase".to_string())),
289    /// ));
290    /// nethsm.add_credentials(Credentials::new(
291    ///     "user1".parse()?,
292    ///     Some(Passphrase::new("other_passphrase".to_string())),
293    /// ));
294    /// nethsm.add_credentials(Credentials::new("user2".parse()?, None));
295    /// # Ok(())
296    /// # }
297    /// ```
298    pub fn add_credentials(&self, credentials: Credentials) {
299        debug!("Add NetHSM connection credentials for {credentials}");
300
301        self.credentials
302            .borrow_mut()
303            .insert(credentials.user_id.clone(), credentials);
304    }
305
306    /// Removes [`Credentials`] from the list of available and currently used ones.
307    ///
308    /// Removes [`Credentials`] from the list of available ones and if identical unsets the
309    /// ones used for further authentication as well.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm, Passphrase};
315    ///
316    /// # fn main() -> testresult::TestResult {
317    /// let nethsm = NetHsm::new(
318    ///     Connection::new(
319    ///         "https://example.org/api/v1".try_into()?,
320    ///         ConnectionSecurity::Unsafe,
321    ///     ),
322    ///     Some(Credentials::new(
323    ///         "admin".parse()?,
324    ///         Some(Passphrase::new("passphrase".to_string())),
325    ///     )),
326    ///     None,
327    ///     None,
328    /// )?;
329    ///
330    /// // remove credentials
331    /// nethsm.remove_credentials(&"admin".parse()?);
332    /// # Ok(())
333    /// # }
334    /// ```
335    pub fn remove_credentials(&self, user_id: &UserId) {
336        debug!("Remove NetHSM connection credentials for {user_id}");
337
338        self.credentials.borrow_mut().remove(user_id);
339        if self
340            .current_credentials
341            .borrow()
342            .as_ref()
343            .is_some_and(|id| id == user_id)
344        {
345            *self.current_credentials.borrow_mut() = None
346        }
347    }
348
349    /// Sets [`Credentials`] to use for the next connection.
350    ///
351    /// # Errors
352    ///
353    /// An [`Error`] is returned if no [`Credentials`] with the [`UserId`] `user_id` can be found.
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm, Passphrase};
359    ///
360    /// # fn main() -> testresult::TestResult {
361    /// let nethsm = NetHsm::new(
362    ///     Connection::new(
363    ///         "https://example.org/api/v1".try_into()?,
364    ///         ConnectionSecurity::Unsafe,
365    ///     ),
366    ///     None,
367    ///     None,
368    ///     None,
369    /// )?;
370    ///
371    /// // add credentials
372    /// nethsm.add_credentials(Credentials::new(
373    ///     "admin".parse()?,
374    ///     Some(Passphrase::new("passphrase".to_string())),
375    /// ));
376    /// nethsm.add_credentials(Credentials::new(
377    ///     "user1".parse()?,
378    ///     Some(Passphrase::new("other_passphrase".to_string())),
379    /// ));
380    ///
381    /// // use admin credentials
382    /// nethsm.use_credentials(&"admin".parse()?)?;
383    ///
384    /// // use operator credentials
385    /// nethsm.use_credentials(&"user1".parse()?)?;
386    ///
387    /// // this fails, because the user has not been added yet
388    /// assert!(nethsm.use_credentials(&"user2".parse()?).is_err());
389    /// # Ok(())
390    /// # }
391    /// ```
392    pub fn use_credentials(&self, user_id: &UserId) -> Result<(), Error> {
393        debug!("Use NetHSM connection credentials of {user_id}");
394
395        if self.credentials.borrow().contains_key(user_id) {
396            if self.current_credentials.borrow().as_ref().is_none()
397                || self
398                    .current_credentials
399                    .borrow()
400                    .as_ref()
401                    .is_some_and(|id| id != user_id)
402            {
403                *self.current_credentials.borrow_mut() = Some(user_id.to_owned());
404            }
405        } else {
406            return Err(Error::Default(format!(
407                "The credentials for User ID \"{user_id}\" need to be added before they can be used!"
408            )));
409        }
410        Ok(())
411    }
412
413    /// Get the [`UserId`] of the currently used [`Credentials`] for the connection.
414    ///
415    /// # Examples
416    ///
417    /// ```
418    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm};
419    ///
420    /// # fn main() -> testresult::TestResult {
421    /// let nethsm = NetHsm::new(
422    ///     Connection::new(
423    ///         "https://example.org/api/v1".try_into()?,
424    ///         ConnectionSecurity::Unsafe,
425    ///     ),
426    ///     Some(Credentials::new(
427    ///         "admin".parse()?,
428    ///         Some("passphrase".parse()?),
429    ///     )),
430    ///     None,
431    ///     None,
432    /// )?;
433    ///
434    /// // Get current User ID
435    /// assert_eq!(nethsm.get_current_user(), Some("admin".parse()?));
436    /// # Ok(())
437    /// # }
438    /// ```
439    pub fn get_current_user(&self) -> Option<UserId> {
440        trace!(
441            "Get current User ID of NetHSM connection at {}",
442            self.url.borrow()
443        );
444
445        self.current_credentials.borrow().clone()
446    }
447}