signstar_host/configure/host/
yubihsm2.rs1use std::str::FromStr;
4
5use log::{debug, error, info, warn};
6use signstar_common::{backend::BackendType, traits::BackendCheck};
7use signstar_config::{
8 admin_credentials::AdminCredentials,
9 config::{UserBackendConnection, UserBackendConnectionFilter},
10 yubihsm2::{
11 YubiHsm2Backend,
12 YubiHsm2Config,
13 admin_credentials::YubiHsm2AdminCredentials,
14 signstar_yubihsm2_export::{Connection, Credentials},
15 yubihsm2_export::{Connector, Id},
16 },
17};
18
19use crate::{
20 Error,
21 configure::{ConfigurationResult, HostConfiguration},
22};
23
24impl<'config> HostConfiguration<'config> {
25 fn load_yubihsm2_admin_credentials(&self) -> Result<YubiHsm2AdminCredentials, Error> {
31 Ok(
32 YubiHsm2AdminCredentials::load(*self.config().system().admin_secret_handling())
33 .map_err(Box::new)?,
34 )
35 }
36
37 fn create_yubihsm2_non_admin_credentials(&self) -> Result<Vec<Credentials>, Error> {
44 info!("Create new non-administrative user credentials for YubiHSM2.");
45 let user_backend_connections = self.config().user_backend_connections(&[
46 UserBackendConnectionFilter::NonAdmin,
47 UserBackendConnectionFilter::Backend(BackendType::YubiHsm2),
48 ]);
49
50 let credentials_list = {
51 let mut creds_list = Vec::new();
52 for user_backend_connection in user_backend_connections {
53 if let UserBackendConnection::YubiHsm2 { .. } = &user_backend_connection
54 && let Some(creds_per_user) = user_backend_connection
55 .create_non_admin_backend_user_secrets()
56 .map_err(Box::new)?
57 {
58 for credentials in creds_per_user {
59 creds_list.push(Credentials::new(
60 Id::from_str(&credentials.user())?,
63 credentials.passphrase().clone(),
64 ));
65 }
66 }
67 }
68
69 creds_list
70 };
71 debug!(
72 "Created non-administrative credentials for the following IDs: {}",
73 credentials_list
74 .iter()
75 .map(|creds| creds.id().to_string())
76 .collect::<Vec<_>>()
77 .join(", ")
78 );
79
80 Ok(credentials_list)
81 }
82
83 fn available_yubihsm2_connections(config: &YubiHsm2Config) -> Vec<Connector> {
85 info!("Query all YubiHSM2 connections for availability.");
86 config
87 .connections()
88 .iter()
89 .filter_map(|connection| {
90 if connection.is_available() {
91 debug!("Detected available YubiHSM2 connection {connection:?}");
92 Some(Connector::from(connection))
93 } else {
94 warn!("Skipping unavailable YubiHSM2 connection {connection:?}");
95 None
96 }
97 })
98 .collect::<Vec<_>>()
99 }
100
101 fn provisioned_yubihsm2_connections(config: &YubiHsm2Config) -> Vec<&Connection> {
103 info!("Detect all provisioned YubiHSM2 connections.");
104 config
105 .connections()
106 .iter()
107 .filter(|connection| {
108 if connection.is_provisioned() {
109 debug!("Detected provisioned YubiHSM2 connection {connection:?}");
110 true
111 } else {
112 debug!("Skipping unprovisioned YubiHSM2 connection {connection:?}");
113 false
114 }
115 })
116 .collect::<Vec<_>>()
117 }
118
119 fn sync_yubihsm2_backends(
131 &self,
132 available_connections: Vec<Connector>,
133 admin_credentials: &YubiHsm2AdminCredentials,
134 user_credentials: &[Credentials],
135 ) -> Result<(), Error> {
136 let backends = {
137 let mut backends = Vec::new();
138 for nethsm in available_connections.into_iter() {
139 if let Some(backend) =
140 YubiHsm2Backend::new(nethsm, admin_credentials, self.config())
141 .map_err(Box::new)?
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 YubiHSM2 backend {backend:?}",
152 );
153 backend.sync(user_credentials).map_err(Box::new)?;
154 }
155
156 Ok(())
157 }
158
159 pub fn sync_yubihsm2(&self) -> Result<ConfigurationResult, Error> {
168 info!("Sync the state of the Signstar configuration with all available YubiHSM2 backends.");
169 let Some(yubihsm2_config) = self.config().yubihsm2() else {
170 warn!("There is no YubiHSM2 section in the Signstar configuration. Skipping...");
171 return Ok(ConfigurationResult::MissingConfigurationForBackend);
172 };
173 info!("Found YubiHSM2 section in the Signstar configuration.");
174
175 let available_connections = {
176 let available_connections = Self::available_yubihsm2_connections(yubihsm2_config);
177 if available_connections.is_empty() {
178 error!("There are no available YubiHSM2 connections. Aborting...");
179 return Ok(ConfigurationResult::NoAvailableBackendConnection);
180 }
181
182 available_connections
183 };
184 let provisioned_backends = Self::provisioned_yubihsm2_connections(yubihsm2_config);
185
186 let (admin_credentials, new_admin_credentials) = match self
187 .load_yubihsm2_admin_credentials()
188 {
189 Ok(admin_credentials) => {
190 info!("Found administrative credentials for YubiHSM2.");
191 (admin_credentials, false)
192 }
193 Err(Error::SignstarConfig(signstar_config_error)) => {
194 if !matches!(
195 *signstar_config_error,
196 signstar_config::Error::AdminSecretHandling(
197 signstar_config::admin_credentials::Error::CredsFileMissing { .. },
198 )
199 ) {
200 return Err(signstar_config_error.into());
201 }
202
203 if !provisioned_backends.is_empty() {
204 error!(
205 "There are provisioned backends, but administrative credentials are not present. You probably want to restore from a backup. Aborting..."
206 );
207 return Ok(ConfigurationResult::ProvisionedBackendsButNoAdminCreds);
208 }
209 if available_connections.len() != yubihsm2_config.connections().len() {
210 error!(
211 "Not all configured connections are (yet) available ({}/{}) and administrative credentials are not present. Aborting...",
212 available_connections.len(),
213 yubihsm2_config.connections().len()
214 );
215 return Ok(ConfigurationResult::NotAllConnectionsAvailableAndNoAdminCreds);
216 }
217
218 (
221 YubiHsm2AdminCredentials::try_from(self.config()).map_err(Box::new)?,
222 true,
223 )
224 }
225 Err(error) => return Err(error),
226 };
227
228 if new_admin_credentials {
230 admin_credentials
231 .store(*self.config().system().admin_secret_handling())
232 .map_err(Box::new)?;
233 }
234
235 let user_credentials = self.create_yubihsm2_non_admin_credentials()?;
237
238 self.sync_yubihsm2_backends(
239 available_connections,
240 &admin_credentials,
241 user_credentials.as_slice(),
242 )?;
243
244 Ok(ConfigurationResult::SyncSucceeded)
245 }
246}