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