Skip to main content

signstar_config/
test.rs

1//! Utilities used for test setups.
2
3use std::{
4    fs::{File, Permissions, create_dir_all, read_dir, set_permissions, write},
5    io::Write,
6    os::{linux::fs::MetadataExt, unix::fs::PermissionsExt},
7    path::{Path, PathBuf},
8    process::{Child, Command},
9    str::FromStr,
10    thread,
11    time,
12};
13
14use change_user_run::{create_users, get_command};
15use log::debug;
16#[cfg(feature = "nethsm")]
17use nethsm::{FullCredentials, UserId};
18use signstar_common::system_user::get_home_base_dir_path;
19#[cfg(feature = "nethsm")]
20use signstar_crypto::AdministrativeSecretHandling;
21#[cfg(feature = "nethsm")]
22use signstar_crypto::passphrase::Passphrase;
23use tempfile::NamedTempFile;
24
25use crate::config::{Config, ConfigSystemUserIds, MappingAuthorizedKeyEntry};
26#[cfg(feature = "nethsm")]
27use crate::{admin_credentials::AdminCredentials, nethsm::NetHsmAdminCredentials};
28/// When any of the HSM backends is present.
29#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
30pub mod impl_any {
31    use super::*;
32    use crate::config::UserBackendConnectionFilter;
33
34    impl SystemUserConfig {
35        /// Applies the chosen system user configuration items based on a [`Config`].
36        ///
37        /// # Errors
38        ///
39        /// Returns an error if secrets for a non-administrative backend user cannot be created.
40        pub fn apply(&self, config: &Config) -> Result<(), crate::Error> {
41            if self.create_secrets {
42                let user_backend_connections =
43                    config.user_backend_connections(&[UserBackendConnectionFilter::NonAdmin]);
44
45                for user_backend_connection in user_backend_connections {
46                    user_backend_connection.create_non_admin_backend_user_secrets()?;
47                }
48            }
49
50            if self.create_ssh_authorized_keys {
51                let user_backend_connections =
52                    config.user_backend_connections(&[UserBackendConnectionFilter::NonAdmin]);
53                for user_backend_connection in user_backend_connections {
54                    user_backend_connection.write_authorized_key_entry()?;
55                }
56
57                for mapping in config.system().mappings() {
58                    mapping.write_authorized_key_entry()?;
59                }
60            }
61
62            Ok(())
63        }
64    }
65}
66
67/// When no HSM backend is present.
68#[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
69mod impl_none {
70    use super::*;
71
72    impl SystemUserConfig {
73        /// Applies the chosen system user configuration items based on a [`Config`].
74        ///
75        /// # Note
76        ///
77        /// Without any HSM backends, no backend-related actions are taken.
78        ///
79        /// # Errors
80        ///
81        /// Returns an error if an `authorized_keys` file for a user cannot be written.
82        pub fn apply(&self, config: &Config) -> Result<(), crate::Error> {
83            if self.create_ssh_authorized_keys {
84                for mapping in config.system().mappings() {
85                    mapping.write_authorized_key_entry()?;
86                }
87            }
88
89            Ok(())
90        }
91    }
92}
93
94/// Config with no HSM backend.
95///
96/// - plaintext for administrative secrets
97/// - plaintext for non-administrative secrets
98const NO_BACKEND_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT: &[u8] =
99    include_bytes!("../../fixtures/config/no_backend/admin-plaintext-non-admin-plaintext.yaml");
100
101/// Config with no HSM backend.
102///
103/// - plaintext for administrative secrets
104/// - systemd-creds for non-administrative secrets
105const NO_BACKEND_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS: &[u8] =
106    include_bytes!("../../fixtures/config/no_backend/admin-plaintext-non-admin-systemd-creds.yaml");
107
108/// Config with no HSM backend.
109///
110/// - systemd-creds for administrative secrets
111/// - plaintext for non-administrative secrets
112const NO_BACKEND_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT: &[u8] =
113    include_bytes!("../../fixtures/config/no_backend/admin-systemd-creds-non-admin-plaintext.yaml");
114
115/// Config with no HSM backend.
116///
117/// - systemd-creds for administrative secrets
118/// - systemd-creds for non-administrative secrets
119const NO_BACKEND_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
120    "../../fixtures/config/no_backend/admin-systemd-creds-non-admin-systemd-creds.yaml"
121);
122
123/// Config with no HSM backend.
124///
125/// - Shamir's Secret Sharing for administrative secrets
126/// - plaintext for non-administrative secrets
127const NO_BACKEND_ADMIN_SSS_NON_ADMIN_PLAINTEXT: &[u8] =
128    include_bytes!("../../fixtures/config/no_backend/admin-sss-non-admin-plaintext.yaml");
129
130/// Config with no HSM backend.
131///
132/// - Shamir's Secret Sharing for administrative secrets
133/// - systemd-creds for non-administrative secrets
134const NO_BACKEND_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS: &[u8] =
135    include_bytes!("../../fixtures/config/no_backend/admin-sss-non-admin-systemd-creds.yaml");
136
137/// Config with NetHSM backend.
138///
139/// - plaintext for administrative secrets
140/// - plaintext for non-administrative secrets
141const ONLY_NETHSM_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT: &[u8] =
142    include_bytes!("../../fixtures/config/nethsm_backend/admin-plaintext-non-admin-plaintext.yaml");
143
144/// Config with NetHSM backend.
145///
146/// - plaintext for administrative secrets
147/// - systemd-creds for non-administrative secrets
148const ONLY_NETHSM_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
149    "../../fixtures/config/nethsm_backend/admin-plaintext-non-admin-systemd-creds.yaml"
150);
151
152/// Config with NetHSM backend.
153///
154/// - systemd-creds for administrative secrets
155/// - plaintext for non-administrative secrets
156const ONLY_NETHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
157    "../../fixtures/config/nethsm_backend/admin-systemd-creds-non-admin-plaintext.yaml"
158);
159
160/// Config with NetHSM backend.
161///
162/// - systemd-creds for administrative secrets
163/// - systemd-creds for non-administrative secrets
164const ONLY_NETHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
165    "../../fixtures/config/nethsm_backend/admin-systemd-creds-non-admin-systemd-creds.yaml"
166);
167
168/// Config with NetHSM backend.
169///
170/// - Shamir's Secret Sharing for administrative secrets
171/// - plaintext for non-administrative secrets
172const ONLY_NETHSM_ADMIN_SSS_NON_ADMIN_PLAINTEXT: &[u8] =
173    include_bytes!("../../fixtures/config/nethsm_backend/admin-sss-non-admin-plaintext.yaml");
174
175/// Config with NetHSM backend.
176///
177/// - Shamir's Secret Sharing for administrative secrets
178/// - systemd-creds for non-administrative secrets
179const ONLY_NETHSM_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS: &[u8] =
180    include_bytes!("../../fixtures/config/nethsm_backend/admin-sss-non-admin-systemd-creds.yaml");
181
182/// Config with YubiHSM2 backend.
183///
184/// - plaintext for administrative secrets
185/// - plaintext for non-administrative secrets
186const ONLY_YUBIHSM2_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
187    "../../fixtures/config/yubihsm2_backend/admin-plaintext-non-admin-plaintext.yaml"
188);
189
190/// Config with YubiHSM2 backend.
191///
192/// - plaintext for administrative secrets
193/// - systemd-creds for non-administrative secrets
194const ONLY_YUBIHSM2_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
195    "../../fixtures/config/yubihsm2_backend/admin-plaintext-non-admin-systemd-creds.yaml"
196);
197
198/// Config with YubiHSM2 backend.
199///
200/// - systemd-creds for administrative secrets
201/// - plaintext for non-administrative secrets
202const ONLY_YUBIHSM2_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
203    "../../fixtures/config/yubihsm2_backend/admin-systemd-creds-non-admin-plaintext.yaml"
204);
205
206/// Config with YubiHSM2 backend.
207///
208/// - systemd-creds for administrative secrets
209/// - systemd-creds for non-administrative secrets
210const ONLY_YUBIHSM2_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
211    "../../fixtures/config/yubihsm2_backend/admin-systemd-creds-non-admin-systemd-creds.yaml"
212);
213
214/// Config with YubiHSM2 backend.
215///
216/// - Shamir's Secret Sharing for administrative secrets
217/// - plaintext for non-administrative secrets
218const ONLY_YUBIHSM2_ADMIN_SSS_NON_ADMIN_PLAINTEXT: &[u8] =
219    include_bytes!("../../fixtures/config/yubihsm2_backend/admin-sss-non-admin-plaintext.yaml");
220
221/// Config with YubiHSM2 backend.
222///
223/// - Shamir's Secret Sharing for administrative secrets
224/// - systemd-creds for non-administrative secrets
225const ONLY_YUBIHSM2_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS: &[u8] =
226    include_bytes!("../../fixtures/config/yubihsm2_backend/admin-sss-non-admin-systemd-creds.yaml");
227
228/// Config with YubiHSM2 mockhsm backend.
229///
230/// - plaintext for administrative secrets
231/// - plaintext for non-administrative secrets
232const ONLY_YUBIHSM2_MOCKHSM_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
233    "../../fixtures/config/yubihsm2_mockhsm_backend/admin-plaintext-non-admin-plaintext.yaml"
234);
235
236/// Config with YubiHSM2 mockhsm backend.
237///
238/// - plaintext for administrative secrets
239/// - systemd-creds for non-administrative secrets
240const ONLY_YUBIHSM2_MOCKHSM_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
241    "../../fixtures/config/yubihsm2_mockhsm_backend/admin-plaintext-non-admin-systemd-creds.yaml"
242);
243
244/// Config with YubiHSM2 mockhsm backend.
245///
246/// - systemd-creds for administrative secrets
247/// - plaintext for non-administrative secrets
248const ONLY_YUBIHSM2_MOCKHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
249    "../../fixtures/config/yubihsm2_mockhsm_backend/admin-systemd-creds-non-admin-plaintext.yaml"
250);
251
252/// Config with YubiHSM2 mockhsm backend.
253///
254/// - systemd-creds for administrative secrets
255/// - systemd-creds for non-administrative secrets
256const ONLY_YUBIHSM2_MOCKHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
257    "../../fixtures/config/yubihsm2_mockhsm_backend/admin-systemd-creds-non-admin-systemd-creds.yaml"
258);
259
260/// Config with YubiHSM2 mockhsm backend.
261///
262/// - Shamir's Secret Sharing for administrative secrets
263/// - plaintext for non-administrative secrets
264const ONLY_YUBIHSM2_MOCKHSM_ADMIN_SSS_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
265    "../../fixtures/config/yubihsm2_mockhsm_backend/admin-sss-non-admin-plaintext.yaml"
266);
267
268/// Config with YubiHSM2 mockhsm backend.
269///
270/// - Shamir's Secret Sharing for administrative secrets
271/// - systemd-creds for non-administrative secrets
272const ONLY_YUBIHSM2_MOCKHSM_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
273    "../../fixtures/config/yubihsm2_mockhsm_backend/admin-sss-non-admin-systemd-creds.yaml"
274);
275
276/// Config with NetHSM and YubiHSM2 backends.
277///
278/// - plaintext for administrative secrets
279/// - plaintext for non-administrative secrets
280const ALL_BACKENDS_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT: &[u8] =
281    include_bytes!("../../fixtures/config/all_backends/admin-plaintext-non-admin-plaintext.yaml");
282
283/// Config with NetHSM and YubiHSM2 backends.
284///
285/// - plaintext for administrative secrets
286/// - systemd-creds for non-administrative secrets
287const ALL_BACKENDS_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
288    "../../fixtures/config/all_backends/admin-plaintext-non-admin-systemd-creds.yaml"
289);
290
291/// Config with NetHSM and YubiHSM2 backends.
292///
293/// - systemd-creds for administrative secrets
294/// - plaintext for non-administrative secrets
295const ALL_BACKENDS_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT: &[u8] = include_bytes!(
296    "../../fixtures/config/all_backends/admin-systemd-creds-non-admin-plaintext.yaml"
297);
298
299/// Config with NetHSM and YubiHSM2 backends.
300///
301/// - systemd-creds for administrative secrets
302/// - systemd-creds for non-administrative secrets
303const ALL_BACKENDS_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS: &[u8] = include_bytes!(
304    "../../fixtures/config/all_backends/admin-systemd-creds-non-admin-systemd-creds.yaml"
305);
306
307/// Config with NetHSM and YubiHSM2 backends.
308///
309/// - Shamir's Secret Sharing for administrative secrets
310/// - plaintext for non-administrative secrets
311const ALL_BACKENDS_ADMIN_SSS_NON_ADMIN_PLAINTEXT: &[u8] =
312    include_bytes!("../../fixtures/config/all_backends/admin-sss-non-admin-plaintext.yaml");
313
314/// Config with NetHSM and YubiHSM2 backends.
315///
316/// - Shamir's Secret Sharing for administrative secrets
317/// - systemd-creds for non-administrative secrets
318const ALL_BACKENDS_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS: &[u8] =
319    include_bytes!("../../fixtures/config/all_backends/admin-sss-non-admin-systemd-creds.yaml");
320
321/// An error that may occur when using test utils.
322#[derive(Debug, thiserror::Error)]
323pub enum Error {
324    /// A timeout has been reached.
325    #[error("Timeout of {timeout}ms reached while {context}")]
326    Timeout {
327        /// The value of the timeout in milliseconds.
328        timeout: u64,
329
330        /// The short description of the operation.
331        context: String,
332    },
333}
334
335/// The targeted location for a Signstar configuration file.
336#[derive(Clone, Copy, Debug, Default)]
337pub enum ConfigFileLocation {
338    /// The override location in `/run/signstar/`.
339    Run,
340
341    /// The override location in `/etc/signstar/`.
342    Etc,
343
344    /// The default location in `/usr/share/signstar/`.
345    #[default]
346    UsrShare,
347}
348
349impl ConfigFileLocation {
350    /// Returns the path of the configuration file's parent directory.
351    pub fn to_parent_dir_path(&self) -> PathBuf {
352        match self {
353            ConfigFileLocation::Run => PathBuf::from(Config::RUN_OVERRIDE_CONFIG_DIR),
354            ConfigFileLocation::Etc => PathBuf::from(Config::ETC_OVERRIDE_CONFIG_DIR),
355            ConfigFileLocation::UsrShare => PathBuf::from(Config::DEFAULT_CONFIG_DIR),
356        }
357    }
358}
359
360impl From<ConfigFileLocation> for PathBuf {
361    fn from(value: ConfigFileLocation) -> Self {
362        value
363            .to_parent_dir_path()
364            .join(format!("{}.yaml", Config::CONFIG_NAME))
365    }
366}
367
368/// The Signstar configuration file variant used for the file contents.
369#[derive(Clone, Copy, Debug, Default)]
370pub enum ConfigFileVariant {
371    /// No HSM backend.
372    ///
373    /// - plaintext for administrative secrets
374    /// - plaintext for non-administrative secrets
375    NoBackendAdminPlaintextNonAdminPlaintext,
376
377    /// No HSM backend.
378    ///
379    /// - plaintext for administrative secrets
380    /// - systemd-creds for non-administrative secrets
381    NoBackendAdminPlaintextNonAdminSystemdCreds,
382
383    /// No HSM backend.
384    ///
385    /// - systemd-creds for administrative secrets
386    /// - plaintext for non-administrative secrets
387    NoBackendAdminSystemdCredsNonAdminPlaintext,
388
389    /// No HSM backend.
390    ///
391    /// - systemd-creds for administrative secrets
392    /// - systemd-creds for non-administrative secrets
393    NoBackendAdminSystemdCredsNonAdminSystemdCreds,
394
395    /// No HSM backend.
396    ///
397    /// - Shamir's Secret Sharing for administrative secrets
398    /// - plaintext for non-administrative secrets
399    NoBackendAdminSssNonAdminPlaintext,
400
401    /// No HSM backend.
402    ///
403    /// - Shamir's Secret Sharing for administrative secrets
404    /// - systemd-creds for non-administrative secrets
405    NoBackendAdminSssNonAdminSystemdCreds,
406
407    /// NetHSM backend.
408    ///
409    /// - plaintext for administrative secrets
410    /// - plaintext for non-administrative secrets
411    OnlyNetHsmBackendAdminPlaintextNonAdminPlaintext,
412
413    /// NetHSM backend.
414    ///
415    /// - plaintext for administrative secrets
416    /// - systemd-creds for non-administrative secrets
417    OnlyNetHsmBackendAdminPlaintextNonAdminSystemdCreds,
418
419    /// NetHSM backend.
420    ///
421    /// - systemd-creds for administrative secrets
422    /// - plaintext for non-administrative secrets
423    OnlyNetHsmBackendAdminSystemdCredsNonAdminPlaintext,
424
425    /// NetHSM backend.
426    ///
427    /// - systemd-creds for administrative secrets
428    /// - systemd-creds for non-administrative secrets
429    OnlyNetHsmBackendAdminSystemdCredsNonAdminSystemdCreds,
430
431    /// NetHSM backend.
432    ///
433    /// - Shamir's Secret Sharing for administrative secrets
434    /// - plaintext for non-administrative secrets
435    OnlyNetHsmBackendAdminSssNonAdminPlaintext,
436
437    /// NetHSM backend.
438    ///
439    /// - Shamir's Secret Sharing for administrative secrets
440    /// - systemd-creds for non-administrative secrets
441    OnlyNetHsmBackendAdminSssNonAdminSystemdCreds,
442
443    /// YubiHSM2 backend.
444    ///
445    /// - plaintext for administrative secrets
446    /// - plaintext for non-administrative secrets
447    OnlyYubiHsm2BackendAdminPlaintextNonAdminPlaintext,
448
449    /// YubiHSM2 backend.
450    ///
451    /// - plaintext for administrative secrets
452    /// - systemd-creds for non-administrative secrets
453    OnlyYubiHsm2BackendAdminPlaintextNonAdminSystemdCreds,
454
455    /// YubiHSM2 backend.
456    ///
457    /// - systemd-creds for administrative secrets
458    /// - plaintext for non-administrative secrets
459    OnlyYubiHsm2BackendAdminSystemdCredsNonAdminPlaintext,
460
461    /// YubiHSM2 backend.
462    ///
463    /// - systemd-creds for administrative secrets
464    /// - systemd-creds for non-administrative secrets
465    OnlyYubiHsm2BackendAdminSystemdCredsNonAdminSystemdCreds,
466
467    /// YubiHSM2 backend.
468    ///
469    /// - Shamir's Secret Sharing for administrative secrets
470    /// - plaintext for non-administrative secrets
471    OnlyYubiHsm2BackendAdminSssNonAdminPlaintext,
472
473    /// YubiHSM2 backend.
474    ///
475    /// - Shamir's Secret Sharing for administrative secrets
476    /// - systemd-creds for non-administrative secrets
477    OnlyYubiHsm2BackendAdminSssNonAdminSystemdCreds,
478
479    /// YubiHSM2 mockhsm backend.
480    ///
481    /// - plaintext for administrative secrets
482    /// - plaintext for non-administrative secrets
483    OnlyYubiHsm2MockHsmBackendAdminPlaintextNonAdminPlaintext,
484
485    /// YubiHSM2 mockhsm backend.
486    ///
487    /// - plaintext for administrative secrets
488    /// - systemd-creds for non-administrative secrets
489    OnlyYubiHsm2MockHsmBackendAdminPlaintextNonAdminSystemdCreds,
490
491    /// YubiHSM2 mockhsm backend.
492    ///
493    /// - systemd-creds for administrative secrets
494    /// - plaintext for non-administrative secrets
495    OnlyYubiHsm2MockHsmBackendAdminSystemdCredsNonAdminPlaintext,
496
497    /// YubiHSM2 mockhsm backend.
498    ///
499    /// - systemd-creds for administrative secrets
500    /// - systemd-creds for non-administrative secrets
501    OnlyYubiHsm2MockHsmBackendAdminSystemdCredsNonAdminSystemdCreds,
502
503    /// YubiHSM2 mockhsm backend.
504    ///
505    /// - Shamir's Secret Sharing for administrative secrets
506    /// - plaintext for non-administrative secrets
507    OnlyYubiHsm2MockHsmBackendAdminSssNonAdminPlaintext,
508
509    /// YubiHSM2 mockhsm backend.
510    ///
511    /// - Shamir's Secret Sharing for administrative secrets
512    /// - systemd-creds for non-administrative secrets
513    OnlyYubiHsm2MockHsmBackendAdminSssNonAdminSystemdCreds,
514
515    /// NetHSM and YubiHSM2 backends.
516    ///
517    /// - plaintext for administrative secrets
518    /// - plaintext for non-administrative secrets
519    AllBackendsAdminPlaintextNonAdminPlaintext,
520
521    /// NetHSM and YubiHSM2 backends.
522    ///
523    /// - plaintext for administrative secrets
524    /// - systemd-creds for non-administrative secrets
525    AllBackendsAdminPlaintextNonAdminSystemdCreds,
526
527    /// NetHSM and YubiHSM2 backends.
528    ///
529    /// - systemd-creds for administrative secrets
530    /// - plaintext for non-administrative secrets
531    AllBackendsAdminSystemdCredsNonAdminPlaintext,
532
533    /// NetHSM and YubiHSM2 backends.
534    ///
535    /// - systemd-creds for administrative secrets
536    /// - systemd-creds for non-administrative secrets
537    AllBackendsAdminSystemdCredsNonAdminSystemdCreds,
538
539    /// NetHSM and YubiHSM2 backends.
540    ///
541    /// - Shamir's Secret Sharing for administrative secrets
542    /// - plaintext for non-administrative secrets
543    AllBackendsAdminSssNonAdminPlaintext,
544
545    /// NetHSM and YubiHSM2 backends.
546    ///
547    /// - Shamir's Secret Sharing for administrative secrets
548    /// - systemd-creds for non-administrative secrets
549    #[default]
550    AllBackendsAdminSssNonAdminSystemdCreds,
551}
552
553impl ConfigFileVariant {
554    /// Returns the bytes of a Signstar configuration matching the chosen variant.
555    pub fn as_config_bytes(&self) -> &[u8] {
556        match self {
557            ConfigFileVariant::NoBackendAdminPlaintextNonAdminPlaintext => {
558                NO_BACKEND_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT
559            }
560            ConfigFileVariant::NoBackendAdminPlaintextNonAdminSystemdCreds => {
561                NO_BACKEND_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS
562            }
563            ConfigFileVariant::NoBackendAdminSystemdCredsNonAdminPlaintext => {
564                NO_BACKEND_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT
565            }
566            ConfigFileVariant::NoBackendAdminSystemdCredsNonAdminSystemdCreds => {
567                NO_BACKEND_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS
568            }
569            ConfigFileVariant::NoBackendAdminSssNonAdminPlaintext => {
570                NO_BACKEND_ADMIN_SSS_NON_ADMIN_PLAINTEXT
571            }
572            ConfigFileVariant::NoBackendAdminSssNonAdminSystemdCreds => {
573                NO_BACKEND_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS
574            }
575            ConfigFileVariant::OnlyNetHsmBackendAdminPlaintextNonAdminPlaintext => {
576                ONLY_NETHSM_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT
577            }
578            ConfigFileVariant::OnlyNetHsmBackendAdminPlaintextNonAdminSystemdCreds => {
579                ONLY_NETHSM_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS
580            }
581            ConfigFileVariant::OnlyNetHsmBackendAdminSystemdCredsNonAdminPlaintext => {
582                ONLY_NETHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT
583            }
584            ConfigFileVariant::OnlyNetHsmBackendAdminSystemdCredsNonAdminSystemdCreds => {
585                ONLY_NETHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS
586            }
587            ConfigFileVariant::OnlyNetHsmBackendAdminSssNonAdminPlaintext => {
588                ONLY_NETHSM_ADMIN_SSS_NON_ADMIN_PLAINTEXT
589            }
590            ConfigFileVariant::OnlyNetHsmBackendAdminSssNonAdminSystemdCreds => {
591                ONLY_NETHSM_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS
592            }
593            ConfigFileVariant::OnlyYubiHsm2BackendAdminPlaintextNonAdminPlaintext => {
594                ONLY_YUBIHSM2_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT
595            }
596            ConfigFileVariant::OnlyYubiHsm2BackendAdminPlaintextNonAdminSystemdCreds => {
597                ONLY_YUBIHSM2_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS
598            }
599            ConfigFileVariant::OnlyYubiHsm2BackendAdminSystemdCredsNonAdminPlaintext => {
600                ONLY_YUBIHSM2_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT
601            }
602            ConfigFileVariant::OnlyYubiHsm2BackendAdminSystemdCredsNonAdminSystemdCreds => {
603                ONLY_YUBIHSM2_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS
604            }
605            ConfigFileVariant::OnlyYubiHsm2BackendAdminSssNonAdminPlaintext => {
606                ONLY_YUBIHSM2_ADMIN_SSS_NON_ADMIN_PLAINTEXT
607            }
608            ConfigFileVariant::OnlyYubiHsm2BackendAdminSssNonAdminSystemdCreds => {
609                ONLY_YUBIHSM2_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS
610            }
611            ConfigFileVariant::OnlyYubiHsm2MockHsmBackendAdminPlaintextNonAdminPlaintext => {
612                ONLY_YUBIHSM2_MOCKHSM_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT
613            }
614            ConfigFileVariant::OnlyYubiHsm2MockHsmBackendAdminPlaintextNonAdminSystemdCreds => {
615                ONLY_YUBIHSM2_MOCKHSM_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS
616            }
617            ConfigFileVariant::OnlyYubiHsm2MockHsmBackendAdminSystemdCredsNonAdminPlaintext => {
618                ONLY_YUBIHSM2_MOCKHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT
619            }
620            ConfigFileVariant::OnlyYubiHsm2MockHsmBackendAdminSystemdCredsNonAdminSystemdCreds => {
621                ONLY_YUBIHSM2_MOCKHSM_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS
622            }
623            ConfigFileVariant::OnlyYubiHsm2MockHsmBackendAdminSssNonAdminPlaintext => {
624                ONLY_YUBIHSM2_MOCKHSM_ADMIN_SSS_NON_ADMIN_PLAINTEXT
625            }
626            ConfigFileVariant::OnlyYubiHsm2MockHsmBackendAdminSssNonAdminSystemdCreds => {
627                ONLY_YUBIHSM2_MOCKHSM_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS
628            }
629            ConfigFileVariant::AllBackendsAdminPlaintextNonAdminPlaintext => {
630                ALL_BACKENDS_ADMIN_PLAINTEXT_NON_ADMIN_PLAINTEXT
631            }
632            ConfigFileVariant::AllBackendsAdminPlaintextNonAdminSystemdCreds => {
633                ALL_BACKENDS_ADMIN_PLAINTEXT_NON_ADMIN_SYSTEMD_CREDS
634            }
635            ConfigFileVariant::AllBackendsAdminSystemdCredsNonAdminPlaintext => {
636                ALL_BACKENDS_ADMIN_SYSTEMD_CREDS_NON_ADMIN_PLAINTEXT
637            }
638            ConfigFileVariant::AllBackendsAdminSystemdCredsNonAdminSystemdCreds => {
639                ALL_BACKENDS_ADMIN_SYSTEMD_CREDS_NON_ADMIN_SYSTEMD_CREDS
640            }
641            ConfigFileVariant::AllBackendsAdminSssNonAdminPlaintext => {
642                ALL_BACKENDS_ADMIN_SSS_NON_ADMIN_PLAINTEXT
643            }
644            ConfigFileVariant::AllBackendsAdminSssNonAdminSystemdCreds => {
645                ALL_BACKENDS_ADMIN_SSS_NON_ADMIN_SYSTEMD_CREDS
646            }
647        }
648    }
649
650    /// Creates a [`Config`] from the selected configuration variant in `self`.
651    ///
652    /// # Errors
653    ///
654    /// Returns an error if
655    ///
656    /// - the bytes cannot be converted to a valid UTF-8 string
657    /// - a valid [`Config`] cannot be created from the variant
658    pub fn to_config(&self) -> Result<Config, crate::Error> {
659        Config::from_str(
660            &String::from_utf8(self.as_config_bytes().to_vec()).map_err(|source| {
661                crate::Error::Utf8String {
662                    path: PathBuf::from("/dev/null"),
663                    context: "creating a Signstar config object from config fixture bytes"
664                        .to_string(),
665                    source,
666                }
667            })?,
668        )
669    }
670}
671
672/// Configuration for the creation of system users.
673#[derive(Clone, Copy, Debug, Default)]
674pub struct SystemUserConfig {
675    /// Whether to create the secrets for each system user with at least one backend user.
676    #[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
677    pub create_secrets: bool,
678
679    /// Whether to create the SSH authorized keys file for system users.
680    pub create_ssh_authorized_keys: bool,
681}
682
683/// Configuration for how and where to provide a Signstar configuration file.
684#[derive(Clone, Copy, Debug, Default)]
685pub struct ConfigFileConfig {
686    /// The optional location in which the Signstar config is placed.
687    ///
688    /// If `location` is [`None`], the Signstar config is not written to a file.
689    pub location: Option<ConfigFileLocation>,
690
691    /// The variant of Signstar configuration that is added.
692    pub variant: ConfigFileVariant,
693
694    /// The optional configuration for system users.
695    ///
696    /// # Note
697    ///
698    /// When set, this implies the creation of all system users.
699    pub system_user_config: Option<SystemUserConfig>,
700}
701
702/// Creates a configuration file in a location based on [`ConfigFileLocation`] and
703/// [`ConfigFileVariant`].
704///
705/// Creates all parent directories.
706///
707/// # Errors
708///
709/// Returns an error if
710///
711/// - the creation of parent directories fails
712/// - the configuration file cannot be created
713/// - the configuration file cannot be written to
714fn create_config(
715    location: ConfigFileLocation,
716    variant: ConfigFileVariant,
717) -> Result<(), crate::Error> {
718    create_dir_all(location.to_parent_dir_path()).map_err(|source| crate::Error::IoPath {
719        path: location.to_parent_dir_path(),
720        context: "creating the parent directory for the Signstar config",
721        source,
722    })?;
723    let path = PathBuf::from(location);
724
725    let mut file = File::create(&path).map_err(|source| crate::Error::IoPath {
726        path: path.clone(),
727        context: "creating a Signstar configuration file",
728        source,
729    })?;
730    let config_bytes = variant.as_config_bytes();
731    file.write_all(config_bytes)
732        .map_err(|source| crate::Error::IoPath {
733            path,
734            context: "writing data to a Signstar configuration file",
735            source,
736        })?;
737
738    Ok(())
739}
740
741/// Creates all Unix users and their homes based on a [`Config`].
742///
743/// # Errors
744///
745/// Returns an error if any of the Unix users cannot be created.
746fn create_unix_users_and_homes(config: &Config) -> Result<(), crate::Error> {
747    let users = config
748        .system_user_ids()
749        .iter()
750        .cloned()
751        .map(|id| id.as_ref())
752        .collect::<Vec<_>>();
753    Ok(create_users(&users, Some(&get_home_base_dir_path()), None)?)
754}
755
756/// Configuration on how to prepare a system for a test setup.
757#[derive(Clone, Copy, Debug)]
758pub struct SystemPrepareConfig {
759    /// Whether to write an `/etc/machine-id`.
760    pub machine_id: bool,
761
762    /// Whether to start a socket for `io.systemd.Credentials`.
763    pub credentials_socket: bool,
764
765    /// How to handle the Signstar config file.
766    pub signstar_config: ConfigFileConfig,
767}
768
769impl SystemPrepareConfig {
770    /// Applies the chosen system configuration items.
771    ///
772    /// Optionally returns the [`BackgroundProcess`] tracking an `io.systemd.Credentials`
773    /// socket.
774    ///
775    /// # Errors
776    ///
777    /// Returns an error if
778    ///
779    /// - an `/etc/machine-id` file should be written, but [`write_machine_id`] fails
780    /// - an `io.systemd.Credentials` socket should be created, but [`start_credentials_socket`]
781    ///   fails
782    /// - a configuration file should be created, but writing it fails
783    /// - the creation of system users and/or their home directories fails
784    /// - the creation of backend user secrets fails
785    pub fn apply(&self) -> Result<Option<BackgroundProcess>, crate::Error> {
786        if self.machine_id {
787            write_machine_id()?;
788        }
789
790        let background_process = if self.credentials_socket {
791            Some(start_credentials_socket()?)
792        } else {
793            None
794        };
795
796        if let Some(config_file_location) = self.signstar_config.location {
797            create_config(config_file_location, self.signstar_config.variant)?;
798
799            if let Some(system_user_config) = self.signstar_config.system_user_config {
800                let config = Config::from_str(&String::from_utf8_lossy(
801                    self.signstar_config.variant.as_config_bytes(),
802                ))?;
803                create_unix_users_and_homes(&config)?;
804                system_user_config.apply(&config)?;
805            }
806        }
807
808        Ok(background_process)
809    }
810}
811
812impl Default for SystemPrepareConfig {
813    fn default() -> Self {
814        Self {
815            machine_id: true,
816            credentials_socket: true,
817            signstar_config: ConfigFileConfig::default(),
818        }
819    }
820}
821
822/// Recursively lists files, their permissions and ownership.
823pub fn list_files_in_dir(path: impl AsRef<Path>) -> Result<(), crate::Error> {
824    let path = path.as_ref();
825    let entries = read_dir(path).map_err(|source| crate::Error::IoPath {
826        path: path.to_path_buf(),
827        context: "reading its children",
828        source,
829    })?;
830
831    for entry in entries {
832        let entry = entry.map_err(|source| crate::Error::IoPath {
833            path: path.to_path_buf(),
834            context: "getting an entry below it",
835            source,
836        })?;
837        let meta = entry.metadata().map_err(|source| crate::Error::IoPath {
838            path: path.to_path_buf(),
839            context: "getting metadata",
840            source,
841        })?;
842
843        debug!(
844            "{} {}/{} {entry:?}",
845            meta.permissions().mode(),
846            meta.st_uid(),
847            meta.st_gid()
848        );
849
850        if meta.is_dir() {
851            list_files_in_dir(entry.path())?;
852        }
853    }
854
855    Ok(())
856}
857
858/// Returns a configuration file with `data` as contents in a temporary location.
859pub fn get_tmp_config(data: &[u8]) -> Result<NamedTempFile, crate::Error> {
860    let tmp_config = NamedTempFile::new().map_err(|source| crate::Error::Io {
861        context: "creating a temporary configuration file".to_string(),
862        source,
863    })?;
864    write(&tmp_config, data).map_err(|source| crate::Error::IoPath {
865        path: tmp_config.path().to_path_buf(),
866        context: "writing full signstar configuration to temporary file",
867        source,
868    })?;
869    Ok(tmp_config)
870}
871
872/// Writes a dummy `/etc/machine-id`, which is required for systemd-creds.
873///
874/// # Errors
875///
876/// Returns an error if
877///
878/// - a static machine-id can not be written to `/etc/machine-id`,
879/// - or metadata on the created `/etc/machine-id` can not be retrieved.
880pub fn write_machine_id() -> Result<(), crate::Error> {
881    debug!("Write dummy /etc/machine-id, required for systemd-creds");
882    let machine_id = PathBuf::from("/etc/machine-id");
883    std::fs::write(&machine_id, "d3b07384d113edec49eaa6238ad5ff00").map_err(|source| {
884        crate::Error::IoPath {
885            path: machine_id.to_path_buf(),
886            context: "writing machine-id",
887            source,
888        }
889    })?;
890
891    let metadata = machine_id
892        .metadata()
893        .map_err(|source| crate::Error::IoPath {
894            path: machine_id,
895            context: "getting metadata of file",
896            source,
897        })?;
898    debug!(
899        "/etc/machine-id\nmode: {}\nuid: {}\ngid: {}",
900        metadata.permissions().mode(),
901        metadata.st_uid(),
902        metadata.st_gid()
903    );
904    Ok(())
905}
906
907/// A background process.
908///
909/// Tracks a [`Child`] which represents a process that runs in the background.
910/// The background process is automatically killed upon dropping the [`BackgroundProcess`].
911#[derive(Debug)]
912pub struct BackgroundProcess {
913    child: Child,
914    command: String,
915}
916
917impl BackgroundProcess {
918    /// Kills the tracked background process.
919    ///
920    /// # Errors
921    ///
922    /// Returns an error if the process could not be killed.
923    pub fn kill(&mut self) -> Result<(), crate::Error> {
924        self.child.kill().map_err(|source| crate::Error::Io {
925            context: format!("killing process of command \"{}\"", self.command),
926            source,
927        })
928    }
929}
930
931impl Drop for BackgroundProcess {
932    /// Kills the tracked background process when destructing the [`BackgroundProcess`].
933    fn drop(&mut self) {
934        if let Err(error) = self.child.kill() {
935            log::debug!(
936                "Unable to kill background process of command {}:\n{error}",
937                self.command
938            )
939        }
940    }
941}
942
943/// Starts a socket for `io.systemd.Credentials` using `systemd-socket-activate`.
944///
945/// Sets the file mode of the socket to `666` so that all users on the system have access.
946///
947/// # Errors
948///
949/// Returns an error if
950///
951/// - `systemd-socket-activate` is unable to start the required socket,
952/// - one or more files in `/run/systemd` can not be listed,
953/// - applying of permissions on `/run/systemd/io.systemd.Credentials` fails,
954/// - or the socket has not been made available within 10000ms.
955pub fn start_credentials_socket() -> Result<BackgroundProcess, crate::Error> {
956    let systemd_run_path = PathBuf::from("/run/systemd");
957    let socket_path = PathBuf::from("/run/systemd/io.systemd.Credentials");
958    create_dir_all(&systemd_run_path).map_err(|source| crate::Error::IoPath {
959        path: systemd_run_path.clone(),
960        context: "creating the directory",
961        source,
962    })?;
963
964    // Run systemd-socket-activate to provide /run/systemd/io.systemd.Credentials
965    let mut command = Command::new(get_command("systemd-socket-activate")?);
966    let command = command.args([
967        "--listen",
968        "/run/systemd/io.systemd.Credentials",
969        "--accept",
970        "--fdname=varlink",
971        "systemd-creds",
972    ]);
973    let child = command.spawn().map_err(|source| crate::Error::IoPath {
974        path: PathBuf::from("/run/systemd/io.systemd.Credentials"),
975        context: "creating a socket using systemd-socket-activate",
976        source,
977    })?;
978
979    // Set the socket to be writable by all, once it's available.
980    let timeout = 10000;
981    let step = 100;
982    let mut elapsed = 0;
983    let mut permissions_set = false;
984    while elapsed < timeout {
985        if socket_path.exists() {
986            debug!("Found {socket_path:?}");
987            set_permissions(socket_path.as_path(), Permissions::from_mode(0o666)).map_err(
988                |source| crate::Error::IoPath {
989                    path: socket_path.to_path_buf(),
990                    context: "applying permissions",
991                    source,
992                },
993            )?;
994            permissions_set = true;
995            break;
996        } else {
997            thread::sleep(time::Duration::from_millis(step));
998            elapsed += step;
999        }
1000    }
1001    if !permissions_set {
1002        return Err(Error::Timeout {
1003            timeout,
1004            context: format!("waiting for {socket_path:?}"),
1005        }
1006        .into());
1007    }
1008
1009    Ok(BackgroundProcess {
1010        child,
1011        command: format!("{command:?}"),
1012    })
1013}
1014
1015/// Creates an [`AdminCredentials`] from config data.
1016///
1017/// Accepts a byte slice containing configuration data.
1018///
1019/// # Errors
1020///
1021/// Returns an error if
1022///
1023/// - a temporary config file can not be created from `config_data`,
1024/// - an [`AdminCredentials`] can not be created from the temporary config file.
1025#[cfg(feature = "nethsm")]
1026pub fn nethsm_admin_credentials(
1027    config_data: &[u8],
1028) -> Result<NetHsmAdminCredentials, crate::Error> {
1029    let config_file = get_tmp_config(config_data)?;
1030    NetHsmAdminCredentials::load_from_file(
1031        config_file.path(),
1032        AdministrativeSecretHandling::Plaintext,
1033    )
1034}
1035
1036/// Creates a list of [`FullCredentials`] for a list of [`UserId`]s.
1037///
1038/// Creates a 30-char long alphanumeric passphrase for each [`UserId`] in `users` and then
1039/// constructs a [`FullCredentials`].
1040#[cfg(feature = "nethsm")]
1041pub fn create_full_credentials(users: &[UserId]) -> Vec<FullCredentials> {
1042    users
1043        .iter()
1044        .map(|user| FullCredentials::new(user.clone(), Passphrase::generate(Some(30))))
1045        .collect()
1046}