1use std::collections::{BTreeSet, HashSet};
4
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7use signstar_crypto::{AdministrativeSecretHandling, NonAdministrativeSecretHandling};
8
9use crate::{
10 AuthorizedKeyEntry,
11 SystemUserId,
12 config::{
13 ConfigAuthorizedKeyEntries,
14 ConfigSystemUserIds,
15 MappingAuthorizedKeyEntry,
16 MappingSystemUserId,
17 duplicate_authorized_keys,
18 duplicate_system_user_ids,
19 },
20};
21
22#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
28#[serde(rename_all = "snake_case")]
29pub enum SystemUserMapping {
30 ShareHolder {
32 system_user: SystemUserId,
34
35 ssh_authorized_key: AuthorizedKeyEntry,
37 },
38
39 WireGuardDownload {
42 system_user: SystemUserId,
44
45 ssh_authorized_key: AuthorizedKeyEntry,
47 },
48}
49
50impl MappingAuthorizedKeyEntry for SystemUserMapping {
51 fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
52 match self {
53 Self::ShareHolder {
54 ssh_authorized_key, ..
55 }
56 | Self::WireGuardDownload {
57 ssh_authorized_key, ..
58 } => Some(ssh_authorized_key),
59 }
60 }
61}
62
63impl MappingSystemUserId for SystemUserMapping {
64 fn system_user_id(&self) -> Option<&SystemUserId> {
65 match self {
66 Self::ShareHolder { system_user, .. } | Self::WireGuardDownload { system_user, .. } => {
67 Some(system_user)
68 }
69 }
70 }
71}
72
73fn validate_system_config_mappings(
93 admin_secret_handling: &AdministrativeSecretHandling,
94) -> impl FnOnce(&BTreeSet<SystemUserMapping>, &()) -> garde::Result + '_ {
95 move |mappings, _| {
96 let duplicate_system_user_ids = duplicate_system_user_ids(mappings);
98
99 let duplicate_authorized_keys = duplicate_authorized_keys(mappings);
101
102 let num_shares = mappings
104 .iter()
105 .filter(|mapping| matches!(mapping, SystemUserMapping::ShareHolder { .. }))
106 .count();
107
108 let mismatching_sss_shares = match admin_secret_handling {
110 AdministrativeSecretHandling::ShamirsSecretSharing {
111 number_of_shares, ..
112 } => {
113 if number_of_shares.get() > num_shares {
114 Some(format!(
115 "only {num_shares} shareholders, but the SSS setup requires {}",
116 number_of_shares.get()
117 ))
118 } else {
119 None
120 }
121 }
122 AdministrativeSecretHandling::Plaintext => {
123 if num_shares != 0 {
124 Some(format!(
125 "{num_shares} SSS shareholders, but the administrative secret handling is plaintext"
126 ))
127 } else {
128 None
129 }
130 }
131 AdministrativeSecretHandling::SystemdCreds => {
132 if num_shares != 0 {
133 Some(format!(
134 "{num_shares} SSS shareholders, but the administrative secret handling is systemd-creds"
135 ))
136 } else {
137 None
138 }
139 }
140 };
141
142 let messages = [
143 duplicate_system_user_ids,
144 duplicate_authorized_keys,
145 mismatching_sss_shares,
146 ];
147 let error_messages = {
148 let mut error_messages = Vec::new();
149
150 for message in messages.iter().flatten() {
151 error_messages.push(message.as_str());
152 }
153
154 error_messages
155 };
156
157 match error_messages.len() {
158 0 => Ok(()),
159 1 => Err(garde::Error::new(format!(
160 "contains {}",
161 error_messages.join("\n")
162 ))),
163 _ => Err(garde::Error::new(format!(
164 "contains multiple issues:\n⤷ {}",
165 error_messages.join("\n⤷ ")
166 ))),
167 }
168 }
169}
170
171#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, Validate)]
183#[serde(rename_all = "snake_case")]
184pub struct SystemConfig {
185 #[garde(skip)]
186 iteration: u32,
187
188 #[garde(skip)]
189 admin_secret_handling: AdministrativeSecretHandling,
190
191 #[garde(skip)]
192 non_admin_secret_handling: NonAdministrativeSecretHandling,
193
194 #[garde(custom(validate_system_config_mappings(&self.admin_secret_handling)))]
195 mappings: BTreeSet<SystemUserMapping>,
196}
197
198impl SystemConfig {
199 pub fn new(
201 iteration: u32,
202 admin_secret_handling: AdministrativeSecretHandling,
203 non_admin_secret_handling: NonAdministrativeSecretHandling,
204 mappings: BTreeSet<SystemUserMapping>,
205 ) -> Result<Self, crate::Error> {
206 let config = Self {
207 iteration,
208 admin_secret_handling,
209 non_admin_secret_handling,
210 mappings,
211 };
212 config
213 .validate()
214 .map_err(|source| crate::Error::Validation {
215 context: "validating a system configuration object".to_string(),
216 source,
217 })?;
218
219 Ok(config)
220 }
221
222 pub fn iteration(&self) -> u32 {
224 self.iteration
225 }
226
227 pub fn admin_secret_handling(&self) -> &AdministrativeSecretHandling {
229 &self.admin_secret_handling
230 }
231
232 pub fn non_admin_secret_handling(&self) -> &NonAdministrativeSecretHandling {
234 &self.non_admin_secret_handling
235 }
236
237 pub fn mappings(&self) -> &BTreeSet<SystemUserMapping> {
239 &self.mappings
240 }
241}
242
243impl ConfigAuthorizedKeyEntries for SystemConfig {
244 fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
245 self.mappings
246 .iter()
247 .filter_map(|mapping| mapping.authorized_key_entry())
248 .collect()
249 }
250}
251
252impl ConfigSystemUserIds for SystemConfig {
253 fn system_user_ids(&self) -> HashSet<&SystemUserId> {
254 self.mappings
255 .iter()
256 .filter_map(|mapping| mapping.system_user_id())
257 .collect()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use std::{num::NonZeroUsize, thread::current};
264
265 use insta::{assert_snapshot, with_settings};
266 use rstest::{fixture, rstest};
267 use signstar_crypto::secret_file::{SSS_DEFAULT_NUMBER_OF_SHARES, SSS_DEFAULT_THRESHOLD};
268 use testresult::TestResult;
269
270 use super::*;
271
272 const SNAPSHOT_PATH: &str = "fixtures/system_config/";
273
274 #[test]
275 fn administrative_secret_handling_default() {
276 assert_eq!(
277 AdministrativeSecretHandling::default(),
278 AdministrativeSecretHandling::ShamirsSecretSharing {
279 number_of_shares: SSS_DEFAULT_NUMBER_OF_SHARES,
280 threshold: SSS_DEFAULT_THRESHOLD,
281 },
282 )
283 }
284
285 #[rstest]
286 #[case::shamirs_secret_sharing_plaintext(
287 AdministrativeSecretHandling::ShamirsSecretSharing {
288 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
289 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
290 },
291 NonAdministrativeSecretHandling::Plaintext,
292 BTreeSet::from_iter([
293 SystemUserMapping::ShareHolder {
294 system_user: "share-holder1".parse()?,
295 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
296 },
297 SystemUserMapping::ShareHolder {
298 system_user: "share-holder2".parse()?,
299 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
300 },
301 SystemUserMapping::ShareHolder {
302 system_user: "share-holder3".parse()?,
303 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
304 },
305 SystemUserMapping::WireGuardDownload {
306 system_user: "wireguard-downloader".parse()?,
307 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
308 },
309 ]),
310 )]
311 #[case::shamirs_secret_sharing_systemd_creds(
312 AdministrativeSecretHandling::ShamirsSecretSharing {
313 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
314 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
315 },
316 NonAdministrativeSecretHandling::SystemdCreds,
317 BTreeSet::from_iter([
318 SystemUserMapping::ShareHolder {
319 system_user: "share-holder1".parse()?,
320 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
321 },
322 SystemUserMapping::ShareHolder {
323 system_user: "share-holder2".parse()?,
324 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
325 },
326 SystemUserMapping::ShareHolder {
327 system_user: "share-holder3".parse()?,
328 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
329 },
330 SystemUserMapping::WireGuardDownload {
331 system_user: "wireguard-downloader".parse()?,
332 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
333 },
334 ]),
335 )]
336 #[case::systemd_creds_plaintext(
337 AdministrativeSecretHandling::SystemdCreds,
338 NonAdministrativeSecretHandling::Plaintext,
339 BTreeSet::from_iter([
340 SystemUserMapping::WireGuardDownload {
341 system_user: "wireguard-downloader".parse()?,
342 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
343 },
344 ]),
345 )]
346 #[case::systemd_creds_systemd_creds(
347 AdministrativeSecretHandling::SystemdCreds,
348 NonAdministrativeSecretHandling::SystemdCreds,
349 BTreeSet::from_iter([
350 SystemUserMapping::WireGuardDownload {
351 system_user: "wireguard-downloader".parse()?,
352 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
353 },
354 ]),
355 )]
356 #[case::plaintext_plaintext(
357 AdministrativeSecretHandling::Plaintext,
358 NonAdministrativeSecretHandling::Plaintext,
359 BTreeSet::from_iter([
360 SystemUserMapping::WireGuardDownload {
361 system_user: "wireguard-downloader".parse()?,
362 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
363 },
364 ]),
365 )]
366 #[case::plaintext_systemd_creds(
367 AdministrativeSecretHandling::Plaintext,
368 NonAdministrativeSecretHandling::SystemdCreds,
369 BTreeSet::from_iter([
370 SystemUserMapping::WireGuardDownload {
371 system_user: "wireguard-downloader".parse()?,
372 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
373 },
374 ]),
375 )]
376 fn system_config_new_succeeds(
377 #[case] administrative_secret_handling: AdministrativeSecretHandling,
378 #[case] non_administrative_secret_handling: NonAdministrativeSecretHandling,
379 #[case] mappings: BTreeSet<SystemUserMapping>,
380 ) -> TestResult {
381 assert!(
382 SystemConfig::new(
383 1,
384 administrative_secret_handling,
385 non_administrative_secret_handling,
386 mappings,
387 )
388 .is_ok()
389 );
390
391 Ok(())
392 }
393
394 #[rstest]
395 #[case::duplicate_user_ids(
396 "Error message for SystemConfig::new with duplicate system user IDs",
397 AdministrativeSecretHandling::ShamirsSecretSharing {
398 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
399 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
400 },
401 BTreeSet::from_iter([
402 SystemUserMapping::ShareHolder {
403 system_user: "share-holder1".parse()?,
404 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
405 },
406 SystemUserMapping::ShareHolder {
407 system_user: "share-holder1".parse()?,
408 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
409 },
410 SystemUserMapping::ShareHolder {
411 system_user: "share-holder3".parse()?,
412 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
413 },
414 SystemUserMapping::WireGuardDownload {
415 system_user: "wireguard-downloader".parse()?,
416 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
417 },
418 ]),
419 )]
420 #[case::duplicate_ssh_public_keys(
421 "Error message for SystemConfig::new with duplicate SSH public keys as authorized_keys",
422 AdministrativeSecretHandling::ShamirsSecretSharing {
423 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
424 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
425 },
426 BTreeSet::from_iter([
427 SystemUserMapping::ShareHolder {
428 system_user: "share-holder1".parse()?,
429 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
430 },
431 SystemUserMapping::ShareHolder {
432 system_user: "share-holder2".parse()?,
433 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user2@host3".parse()?,
434 },
435 SystemUserMapping::ShareHolder {
436 system_user: "share-holder3".parse()?,
437 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
438 },
439 SystemUserMapping::WireGuardDownload {
440 system_user: "wireguard-downloader".parse()?,
441 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
442 },
443 ]),
444 )]
445 #[case::too_few_sss_shares(
446 "Error message for SystemConfig::new with too few SSS shareholders",
447 AdministrativeSecretHandling::ShamirsSecretSharing {
448 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
449 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
450 },
451 BTreeSet::from_iter([
452 SystemUserMapping::ShareHolder {
453 system_user: "share-holder1".parse()?,
454 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
455 },
456 SystemUserMapping::ShareHolder {
457 system_user: "share-holder2".parse()?,
458 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
459 },
460 SystemUserMapping::WireGuardDownload {
461 system_user: "wireguard-downloader".parse()?,
462 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
463 },
464 ]),
465 )]
466 #[case::plaintext_admin_creds_with_sss_shareholders(
467 "Error message for SystemConfig::new with SSS shareholders but plaintext based admin credentials handling",
468 AdministrativeSecretHandling::Plaintext,
469 BTreeSet::from_iter([
470 SystemUserMapping::ShareHolder {
471 system_user: "share-holder1".parse()?,
472 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
473 },
474 SystemUserMapping::ShareHolder {
475 system_user: "share-holder2".parse()?,
476 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
477 },
478 SystemUserMapping::ShareHolder {
479 system_user: "share-holder3".parse()?,
480 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
481 },
482 SystemUserMapping::WireGuardDownload {
483 system_user: "wireguard-downloader".parse()?,
484 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
485 },
486 ]),
487 )]
488 #[case::systemd_creds_admin_creds_with_sss_shareholders(
489 "Error message for SystemConfig::new with SSS shareholders but systemd-creds based admin credentials handling",
490 AdministrativeSecretHandling::SystemdCreds,
491 BTreeSet::from_iter([
492 SystemUserMapping::ShareHolder {
493 system_user: "share-holder1".parse()?,
494 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
495 },
496 SystemUserMapping::ShareHolder {
497 system_user: "share-holder2".parse()?,
498 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
499 },
500 SystemUserMapping::ShareHolder {
501 system_user: "share-holder3".parse()?,
502 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
503 },
504 SystemUserMapping::WireGuardDownload {
505 system_user: "wireguard-downloader".parse()?,
506 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
507 },
508 ]),
509 )]
510 #[case::multiple_issues(
511 "Error message for SystemConfig::new with SSS shareholders but plaintext based admin credentials handling, duplicate system user IDs and SSH public keys",
512 AdministrativeSecretHandling::SystemdCreds,
513 BTreeSet::from_iter([
514 SystemUserMapping::ShareHolder {
515 system_user: "share-holder1".parse()?,
516 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
517 },
518 SystemUserMapping::ShareHolder {
519 system_user: "share-holder1".parse()?,
520 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user1@host5".parse()?,
521 },
522 SystemUserMapping::ShareHolder {
523 system_user: "wireguard-downloader".parse()?,
524 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user2@host3".parse()?
525 },
526 SystemUserMapping::WireGuardDownload {
527 system_user: "wireguard-downloader".parse()?,
528 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
529 },
530 ]),
531 )]
532 fn system_config_new_fails_validation(
533 #[case] description: &str,
534 #[case] admin_secret_handling: AdministrativeSecretHandling,
535 #[case] mappings: BTreeSet<SystemUserMapping>,
536 ) -> TestResult {
537 let error_msg = match SystemConfig::new(
538 1,
539 admin_secret_handling,
540 NonAdministrativeSecretHandling::default(),
541 mappings,
542 ) {
543 Err(crate::Error::Validation { source, .. }) => source.to_string(),
544 Ok(config) => {
545 panic!(
546 "Expected to fail with Error::Validation, but succeeded instead:
547 {config:?}"
548 )
549 }
550 Err(error) => panic!(
551 "Expected to fail with Error::Validation, but failed with a different error
552 instead: {error}"
553 ),
554 };
555
556 with_settings!({
557 description => description,
558 snapshot_path => SNAPSHOT_PATH,
559 prepend_module_to_snapshot => false,
560 }, {
561 assert_snapshot!(current().name().expect("current thread should have a
562 name").to_string().replace("::", "__"), error_msg); });
563 Ok(())
564 }
565
566 #[fixture]
567 fn administrative_secret_handling() -> AdministrativeSecretHandling {
568 AdministrativeSecretHandling::default()
569 }
570
571 #[fixture]
572 fn non_administrative_secret_handling() -> NonAdministrativeSecretHandling {
573 NonAdministrativeSecretHandling::default()
574 }
575
576 #[fixture]
577 fn mappings() -> TestResult<BTreeSet<SystemUserMapping>> {
578 Ok(BTreeSet::from_iter([
579 SystemUserMapping::ShareHolder {
580 system_user: "share-holder1".parse()?,
581 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?
582 },
583 SystemUserMapping::ShareHolder {
584 system_user: "share-holder2".parse()?,
585 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
586 },
587 SystemUserMapping::ShareHolder {
588 system_user: "share-holder3".parse()?,
589 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
590 },
591 SystemUserMapping::ShareHolder {
592 system_user: "share-holder4".parse()?,
593 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINsej5PBntjmthtYKXUrPKwYKadruZMhvZE3EmVxbOwL user@host".parse()?
594 },
595 SystemUserMapping::ShareHolder {
596 system_user: "share-holder5".parse()?,
597 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJMmh08ZQTPRQS9NDNJY6zRVdjwSBwcPcefiXnAEtsgE user@host".parse()?
598 },
599 SystemUserMapping::ShareHolder {
600 system_user: "share-holder6".parse()?,
601 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJAW0YOVnJHm5qqiZBvIwPc0GH1D7ALDGwDRsBZHWbGU user@host".parse()?
602 },
603 SystemUserMapping::WireGuardDownload {
604 system_user: "wireguard-downloader".parse()?,
605 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
606 },
607 ]))
608 }
609
610 #[fixture]
611 fn system_config(
612 administrative_secret_handling: AdministrativeSecretHandling,
613 non_administrative_secret_handling: NonAdministrativeSecretHandling,
614 mappings: TestResult<BTreeSet<SystemUserMapping>>,
615 ) -> TestResult<SystemConfig> {
616 let mappings = mappings?;
617 Ok(SystemConfig::new(
618 1,
619 administrative_secret_handling,
620 non_administrative_secret_handling,
621 mappings,
622 )?)
623 }
624
625 #[rstest]
626 fn system_config_iteration(system_config: TestResult<SystemConfig>) -> TestResult {
627 let system_config = system_config?;
628 assert_eq!(system_config.iteration(), 1);
629
630 Ok(())
631 }
632
633 #[rstest]
634 fn system_config_admin_secret_handling(
635 system_config: TestResult<SystemConfig>,
636 administrative_secret_handling: AdministrativeSecretHandling,
637 ) -> TestResult {
638 let system_config = system_config?;
639 assert_eq!(
640 system_config.admin_secret_handling(),
641 &administrative_secret_handling
642 );
643
644 Ok(())
645 }
646
647 #[rstest]
648 fn system_config_non_admin_secret_handling(
649 system_config: TestResult<SystemConfig>,
650 non_administrative_secret_handling: NonAdministrativeSecretHandling,
651 ) -> TestResult {
652 let system_config = system_config?;
653 assert_eq!(
654 system_config.non_admin_secret_handling(),
655 &non_administrative_secret_handling
656 );
657
658 Ok(())
659 }
660
661 #[rstest]
662 fn system_config_mappings(
663 system_config: TestResult<SystemConfig>,
664 mappings: TestResult<BTreeSet<SystemUserMapping>>,
665 ) -> TestResult {
666 let system_config = system_config?;
667 let mappings = mappings?;
668 assert_eq!(system_config.mappings(), &mappings);
669
670 Ok(())
671 }
672
673 #[rstest]
674 fn system_config_authorized_key_entries(
675 system_config: TestResult<SystemConfig>,
676 mappings: TestResult<BTreeSet<SystemUserMapping>>,
677 ) -> TestResult {
678 let system_config = system_config?;
679 let mappings = mappings?;
680 let authorized_keys = mappings
681 .iter()
682 .filter_map(|mapping| mapping.authorized_key_entry())
683 .collect::<HashSet<_>>();
684 assert_eq!(system_config.authorized_key_entries(), authorized_keys);
685
686 Ok(())
687 }
688
689 #[rstest]
690 fn system_config_system_user_ids(
691 system_config: TestResult<SystemConfig>,
692 mappings: TestResult<BTreeSet<SystemUserMapping>>,
693 ) -> TestResult {
694 let system_config = system_config?;
695 let mappings = mappings?;
696 let system_user_ids = mappings
697 .iter()
698 .filter_map(|mapping| mapping.system_user_id())
699 .collect::<HashSet<_>>();
700 assert_eq!(system_config.system_user_ids(), system_user_ids);
701
702 Ok(())
703 }
704}