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