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