Skip to main content

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