Skip to main content

signstar_configure/host/
nethsm.rs

1//! Functionality when support for NetHSM backends is compiled in.
2
3use log::{debug, error, info, warn};
4use signstar_common::{backend::BackendType, traits::BackendCheck};
5use signstar_config::{
6    admin_credentials::AdminCredentials,
7    config::{UserBackendConnection, UserBackendConnectionFilter},
8    nethsm::{
9        NetHsmAdminCredentials,
10        NetHsmBackend,
11        NetHsmConfig,
12        nethsm_export::{Connection, FullCredentials, NetHsm, NetHsmError, UserId},
13    },
14};
15
16use crate::{ConfigurationResult, Error, HostConfiguration};
17
18impl<'config> HostConfiguration<'config> {
19    /// Loads [`NetHsmAdminCredentials`].
20    ///
21    /// # Errors
22    ///
23    /// Returns an error if [`NetHsmAdminCredentials::load`] fails.
24    fn load_nethsm_admin_credentials(&self) -> Result<NetHsmAdminCredentials, Error> {
25        Ok(NetHsmAdminCredentials::load(
26            *self.config().system().admin_secret_handling(),
27        )?)
28    }
29
30    /// Returns the list of available connections to NetHSM backends.
31    fn available_nethsm_connections(config: &NetHsmConfig) -> Vec<NetHsm> {
32        info!("Query all NetHSM connections for availability.");
33        config
34            .connections()
35            .iter()
36            .filter_map(|connection| {
37                if connection.is_available() {
38                    // Opportunistically create the network connection, as we want to connect to as
39                    // many as possible.
40                    match NetHsm::new(connection.clone(), None, None, None) {
41                        Err(error) => {
42                            error!(
43                                "Skipping NetHSM connection {} due to an error: {error}",
44                                connection.url()
45                            );
46                            None
47                        }
48                        Ok(nethsm) => {
49                            info!("Detected available NetHSM connection {}", connection.url());
50                            Some(nethsm)
51                        }
52                    }
53                } else {
54                    warn!(
55                        "Skipping unavailable NetHSM connection {}",
56                        connection.url()
57                    );
58                    None
59                }
60            })
61            .collect()
62    }
63
64    /// Returns the list of provisioned NetHSM backend connections.
65    fn provisioned_nethsm_connections(config: &NetHsmConfig) -> Vec<&Connection> {
66        info!("Detect all provisioned NetHSM connections.");
67        config
68            .connections()
69            .iter()
70            .filter(|connection| {
71                if connection.is_provisioned() {
72                    debug!("Detected provisioned NetHSM connection {connection:?}");
73                    true
74                } else {
75                    debug!("Skipping unprovisioned NetHSM connection {connection:?}");
76                    false
77                }
78            })
79            .collect::<Vec<_>>()
80    }
81
82    /// Creates new credentials for all non-administrative NetHSM backend users.
83    ///
84    /// # Errors
85    ///
86    /// Returns an error, if the non-administrative credentials for a specific user cannot be
87    /// created.
88    fn create_nethsm_non_admin_credentials(&self) -> Result<Vec<FullCredentials>, Error> {
89        info!("Create new non-administrative user credentials for NetHSM backends.");
90        let user_backend_connections = self.config().user_backend_connections(&[
91            UserBackendConnectionFilter::NonAdmin,
92            UserBackendConnectionFilter::Backend(BackendType::NetHsm),
93        ]);
94
95        let credentials_list = {
96            let mut creds_list = Vec::new();
97            for user_backend_connection in user_backend_connections {
98                if let UserBackendConnection::NetHsm { .. } = &user_backend_connection
99                    && let Some(creds_per_user) =
100                        user_backend_connection.create_non_admin_backend_user_secrets()?
101                {
102                    for credentials in creds_per_user {
103                        creds_list.push(FullCredentials::new(
104                            // NOTE: Here we cannot fail, because we already know that
105                            // the user name is valid.
106                            UserId::try_from(credentials.user()).map_err(|source| {
107                                signstar_config::Error::NetHsm(NetHsmError::User(source))
108                            })?,
109                            credentials.passphrase().clone(),
110                        ));
111                    }
112                }
113            }
114
115            creds_list
116        };
117
118        Ok(credentials_list)
119    }
120
121    /// Syncs all available NetHSM backends.
122    ///
123    /// Returns early success, if there is no NetHSM section in the Signstar config.
124    ///
125    /// # Errors
126    ///
127    /// Returns an error, if
128    ///
129    /// - [`NetHsmAdminCredentials::load`] cannot load administrative credentials
130    /// - a [`NetHsmBackend`] cannot be created for a connection
131    /// - running [`NetHsmBackend::sync`] for a specific backend fails
132    fn sync_nethsm_backends(
133        &self,
134        available_connections: Vec<NetHsm>,
135        admin_credentials: &NetHsmAdminCredentials,
136        user_credentials: &[FullCredentials],
137    ) -> Result<(), Error> {
138        let backends = {
139            let mut backends = Vec::new();
140            for nethsm in available_connections.into_iter() {
141                if let Some(backend) = NetHsmBackend::new(nethsm, admin_credentials, self.config())?
142                {
143                    backends.push(backend);
144                }
145            }
146            backends
147        };
148
149        for backend in backends {
150            info!(
151                "Sync the state of the Signstar configuration with the NetHSM backend {}",
152                backend.nethsm().get_url()
153            );
154            backend.sync(user_credentials)?;
155        }
156
157        Ok(())
158    }
159
160    /// Syncs the states of all available NetHSM backends with that of the Signstar configuration.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error, if
165    ///
166    /// - the creation of non-administrative credentials fails
167    /// - the syncing of the backend fails
168    pub fn sync_nethsm(&self) -> Result<ConfigurationResult, Error> {
169        info!("Sync the state of the Signstar configuration with all available NetHSM backends.");
170        let Some(nethsm_config) = self.config().nethsm() else {
171            warn!("There is no NetHSM section in the Signstar configuration. Skipping...");
172            return Ok(ConfigurationResult::MissingConfigurationForBackend);
173        };
174        info!("Found NetHSM section in the Signstar configuration.");
175
176        let available_connections = {
177            let available_connections = Self::available_nethsm_connections(nethsm_config);
178            if available_connections.is_empty() {
179                error!("There are no available NetHSM connections. Aborting...");
180                return Ok(ConfigurationResult::NoAvailableBackendConnection);
181            }
182
183            available_connections
184        };
185        let provisioned_backends = Self::provisioned_nethsm_connections(nethsm_config);
186
187        let admin_credentials = match self.load_nethsm_admin_credentials() {
188            Ok(admin_credentials) => {
189                info!("Found administrative credentials for NetHSM.");
190                admin_credentials
191            }
192            Err(Error::SignstarConfig(signstar_config::Error::AdminSecretHandling(
193                signstar_config::admin_credentials::Error::CredsFileMissing { .. },
194            ))) => {
195                if !provisioned_backends.is_empty() {
196                    error!(
197                        "There are provisioned backends, but administrative credentials are not present. You probably want to restore from a backup. Aborting..."
198                    );
199                    return Ok(ConfigurationResult::ProvisionedBackendsButNoAdminCreds);
200                }
201                if available_connections.len() != nethsm_config.connections().len() {
202                    error!(
203                        "Not all configured connections are (yet) available ({}/{}) and administrative credentials are not present. Aborting...",
204                        available_connections.len(),
205                        nethsm_config.connections().len()
206                    );
207                    return Ok(ConfigurationResult::NotAllConnectionsAvailableAndNoAdminCreds);
208                }
209
210                // All backends are available and unprovisioned.
211                // There are no administrative credentials (yet), so they are created.
212                NetHsmAdminCredentials::try_from(self.config())?
213            }
214            Err(error) => return Err(error),
215        };
216
217        // Non-administrative credentials are always created from scratch, unconditionally.
218        let user_credentials = self.create_nethsm_non_admin_credentials()?;
219
220        self.sync_nethsm_backends(
221            available_connections,
222            &admin_credentials,
223            user_credentials.as_slice(),
224        )?;
225
226        Ok(ConfigurationResult::SyncSucceeded)
227    }
228}