signstar_config/config/traits.rs
1//! Traits for configuration use.
2
3use std::collections::HashSet;
4
5use nix::unistd::User;
6use signstar_crypto::{
7 NonAdministrativeSecretHandling,
8 passphrase::Passphrase,
9 secret_file::{load_passphrase_from_secrets_file, write_passphrase_to_secrets_file},
10 traits::UserWithPassphrase,
11};
12
13use crate::{AuthorizedKeyEntry, SystemUserId, utils::get_current_system_user};
14
15/// An error that may occur when using signstar-config traits.
16#[derive(Debug, thiserror::Error)]
17pub enum Error {
18 /// A backend user ID does not match.
19 #[error("Expected the backend user ID {expected}, but found {actual} instead")]
20 BackendUserIdMismatch {
21 /// The expected backend user ID.
22 expected: String,
23
24 /// The actually found backend user ID.
25 actual: String,
26 },
27}
28
29/// An interface for returning an optional [`SystemUserId`] or a [`User`].
30///
31/// It is implemented by mapping implementations, that track system user data.
32///
33/// # Example
34///
35/// ```
36/// use signstar_config::{SystemUserId, config::MappingSystemUserId};
37/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
38///
39/// #[derive(Debug)]
40/// enum ExampleUserMapping {
41/// Admin {
42/// backend_id: u8,
43/// },
44/// Backup {
45/// backend_id: u8,
46/// system_user: SystemUserId,
47/// },
48/// Metrics {
49/// backend_id: u8,
50/// system_user: SystemUserId,
51/// },
52/// Signer {
53/// backend_id: u8,
54/// system_user: SystemUserId,
55/// },
56/// }
57///
58/// impl ExampleUserMapping {
59/// pub fn backend_user_id(&self) -> u8 {
60/// match self {
61/// Self::Admin { backend_id }
62/// | Self::Backup { backend_id, .. }
63/// | Self::Metrics { backend_id, .. }
64/// | Self::Signer { backend_id, .. } => *backend_id,
65/// }
66/// }
67/// }
68///
69/// impl MappingSystemUserId for ExampleUserMapping {
70/// fn system_user_id(&self) -> Option<&SystemUserId> {
71/// match self {
72/// Self::Admin { .. } => None,
73/// Self::Backup { system_user, .. }
74/// | Self::Metrics { system_user, .. }
75/// | Self::Signer { system_user, .. } => Some(system_user),
76/// }
77/// }
78/// }
79/// ```
80pub trait MappingSystemUserId {
81 /// Returns a reference to the [`SystemUserId`].
82 ///
83 /// # Note
84 ///
85 /// Should return [`None`], if the user mapping implementation does not track a system user.
86 fn system_user_id(&self) -> Option<&SystemUserId>;
87
88 /// Returns the tracked system user ID as [`User`] if it exists.
89 ///
90 /// This is a default implementation and should require no specific implementation.
91 ///
92 /// # Note
93 ///
94 /// Returns `Ok(None)`, if [`MappingSystemUserId::system_user_id`] returns [`None`] (the user
95 /// mapping implementation tracks no system user).
96 ///
97 /// # Errors
98 ///
99 /// Returns an error if no Unix user of the mapping's system user name exists.
100 fn system_user_id_as_existing_unix_user(&self) -> Result<Option<User>, crate::Error> {
101 let Some(system_user_id) = self.system_user_id() else {
102 return Ok(None);
103 };
104
105 // NOTE: We ignore the potential `None` return value of `User::from_name` because it would
106 // mean an invalid system user name (which cannot happen due to validation).
107 Ok(User::from_name(system_user_id.as_ref()).map_err(|source| {
108 crate::utils::Error::SystemUserLookup {
109 user: crate::utils::NameOrUid::Name(system_user_id.clone()),
110 source,
111 }
112 })?)
113 }
114
115 /// Returns the tracked system user ID as the current [`User`] if it exists.
116 ///
117 /// This is a default implementation and should require no specific implementation.
118 ///
119 /// # Note
120 ///
121 /// Returns `Ok(None)`, if [`MappingSystemUserId::system_user_id`] returns [`None`] (the user
122 /// mapping implementation tracks no system user).
123 ///
124 /// # Errors
125 ///
126 /// Returns an error if
127 ///
128 /// - retrieving the effective User ID of the current Unix user fails,
129 /// - the currently calling system user does not match the one returned by
130 /// [`MappingSystemUserId::system_user_id`],
131 fn system_user_id_as_current_unix_user(&self) -> Result<Option<User>, crate::Error> {
132 let Some(system_user_id) = self.system_user_id() else {
133 return Ok(None);
134 };
135 let current_system_user = get_current_system_user()?;
136
137 if current_system_user.name != system_user_id.as_ref() {
138 return Err(crate::utils::Error::SystemUserMismatch {
139 target_user: system_user_id.to_string(),
140 current_user: current_system_user.name,
141 }
142 .into());
143 }
144
145 Ok(Some(current_system_user))
146 }
147}
148
149/// The kind of backend user.
150///
151/// This distinguishes between the different access rights levels (i.e. administrative and
152/// non-administrative) and roles (e.g. backup, metrics, signing) of a backend user.
153#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
154pub enum BackendUserIdKind {
155 /// Any user.
156 #[default]
157 Any,
158
159 /// Administrative user.
160 Admin,
161
162 /// Backup user.
163 Backup,
164
165 /// Metrics user.
166 Metrics,
167
168 /// Any non-administrative user.
169 NonAdmin,
170
171 /// User used to observe keys, without access to them.
172 Observer,
173
174 /// Signing user.
175 Signing,
176}
177
178/// A filter for user mapping variants.
179#[derive(Clone, Debug, Default, Eq, PartialEq)]
180pub struct BackendUserIdFilter {
181 /// The kind of backend user.
182 pub backend_user_id_kind: BackendUserIdKind,
183}
184
185/// An interface for returning a list of backend users based on a filter.
186///
187/// # Example
188///
189/// ```
190/// use signstar_config::{
191/// Error,
192/// SystemUserId,
193/// config::{BackendUserIdFilter, BackendUserIdKind, MappingBackendUserIds},
194/// };
195/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
196///
197/// #[derive(Debug)]
198/// struct ExampleCreds {
199/// pub id: u8,
200/// pub passphrase: Passphrase,
201/// }
202///
203/// impl UserWithPassphrase for ExampleCreds {
204/// fn user(&self) -> String {
205/// self.id.to_string()
206/// }
207///
208/// fn passphrase(&self) -> &Passphrase {
209/// &self.passphrase
210/// }
211/// }
212///
213/// #[derive(Debug)]
214/// enum ExampleUserMapping {
215/// Admin {
216/// backend_id: u8,
217/// },
218/// Backup {
219/// backend_id: u8,
220/// system_user: SystemUserId,
221/// },
222/// Metrics {
223/// backend_id: u8,
224/// system_user: SystemUserId,
225/// },
226/// Signer {
227/// backend_id: u8,
228/// system_user: SystemUserId,
229/// },
230/// }
231///
232/// impl ExampleUserMapping {
233/// pub fn backend_user_id(&self) -> u8 {
234/// match self {
235/// Self::Admin { backend_id }
236/// | Self::Backup { backend_id, .. }
237/// | Self::Metrics { backend_id, .. }
238/// | Self::Signer { backend_id, .. } => *backend_id,
239/// }
240/// }
241/// }
242///
243/// impl MappingBackendUserIds for ExampleUserMapping {
244/// fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
245/// match self {
246/// Self::Admin { backend_id, .. } => {
247/// if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
248/// .contains(&filter.backend_user_id_kind)
249/// {
250/// return vec![backend_id.to_string()];
251/// }
252/// }
253/// Self::Backup { backend_id, .. } => {
254/// if [
255/// BackendUserIdKind::Any,
256/// BackendUserIdKind::Backup,
257/// BackendUserIdKind::NonAdmin,
258/// ]
259/// .contains(&filter.backend_user_id_kind)
260/// {
261/// return vec![backend_id.to_string()];
262/// }
263/// }
264/// Self::Metrics { backend_id, .. } => {
265/// if [
266/// BackendUserIdKind::Any,
267/// BackendUserIdKind::Metrics,
268/// BackendUserIdKind::NonAdmin,
269/// ]
270/// .contains(&filter.backend_user_id_kind)
271/// {
272/// return vec![backend_id.to_string()];
273/// }
274/// }
275/// Self::Signer { backend_id, .. } => {
276/// if [
277/// BackendUserIdKind::Any,
278/// BackendUserIdKind::Signing,
279/// BackendUserIdKind::NonAdmin,
280/// ]
281/// .contains(&filter.backend_user_id_kind)
282/// {
283/// return vec![backend_id.to_string()];
284/// }
285/// }
286/// }
287///
288/// Vec::new()
289/// }
290///
291/// fn backend_user_with_passphrase(
292/// &self,
293/// name: &str,
294/// passphrase: Passphrase,
295/// ) -> Result<Box<dyn UserWithPassphrase>, Error> {
296/// let backend_user_id = self.backend_user_id();
297/// if backend_user_id.to_string() != name {
298/// return Err(
299/// signstar_config::config::TraitsError::BackendUserIdMismatch {
300/// expected: name.to_string(),
301/// actual: backend_user_id.to_string(),
302/// }
303/// .into(),
304/// );
305/// }
306///
307/// Ok(Box::new(ExampleCreds {
308/// id: backend_user_id,
309/// passphrase,
310/// }))
311/// }
312///
313/// fn backend_users_with_new_passphrase(
314/// &self,
315/// filter: BackendUserIdFilter,
316/// ) -> Vec<Box<dyn UserWithPassphrase>> {
317/// if let Some(backend_id) = match self {
318/// Self::Admin { backend_id, .. } => {
319/// if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
320/// .contains(&filter.backend_user_id_kind)
321/// {
322/// Some(*backend_id)
323/// } else {
324/// None
325/// }
326/// }
327/// Self::Backup { backend_id, .. } => {
328/// if [
329/// BackendUserIdKind::Any,
330/// BackendUserIdKind::Backup,
331/// BackendUserIdKind::NonAdmin,
332/// ]
333/// .contains(&filter.backend_user_id_kind)
334/// {
335/// Some(*backend_id)
336/// } else {
337/// None
338/// }
339/// }
340/// Self::Metrics { backend_id, .. } => {
341/// if [
342/// BackendUserIdKind::Any,
343/// BackendUserIdKind::Metrics,
344/// BackendUserIdKind::NonAdmin,
345/// ]
346/// .contains(&filter.backend_user_id_kind)
347/// {
348/// Some(*backend_id)
349/// } else {
350/// None
351/// }
352/// }
353/// Self::Signer { backend_id, .. } => {
354/// if [
355/// BackendUserIdKind::Any,
356/// BackendUserIdKind::Signing,
357/// BackendUserIdKind::NonAdmin,
358/// ]
359/// .contains(&filter.backend_user_id_kind)
360/// {
361/// Some(*backend_id)
362/// } else {
363/// None
364/// }
365/// }
366/// } {
367/// vec![Box::new(ExampleCreds {
368/// id: backend_id,
369/// passphrase: Passphrase::generate(None),
370/// })]
371/// } else {
372/// Vec::new()
373/// }
374/// }
375/// }
376///
377/// # fn main() -> testresult::TestResult {
378/// let backend_id = 1;
379/// let mapping = ExampleUserMapping::Backup {
380/// backend_id,
381/// system_user: "backup".parse()?,
382/// };
383///
384/// // Find backend user IDs based on the kind of user.
385/// assert!(
386/// mapping
387/// .backend_user_ids(BackendUserIdFilter {
388/// backend_user_id_kind: BackendUserIdKind::Backup
389/// })
390/// .first()
391/// .is_some_and(|id| *id == backend_id.to_string())
392/// );
393/// // This returns an empty list if there are no matches.
394/// assert!(
395/// mapping
396/// .backend_user_ids(BackendUserIdFilter {
397/// backend_user_id_kind: BackendUserIdKind::Admin
398/// })
399/// .is_empty()
400/// );
401///
402/// // Create credentials based on a user and a passphrase.
403/// let passphrase = Passphrase::generate(None);
404/// let creds = mapping.backend_user_with_passphrase("1", passphrase.clone())?;
405/// assert_eq!(creds.user(), backend_id.to_string());
406/// assert_eq!(
407/// creds.passphrase().expose_borrowed(),
408/// passphrase.expose_borrowed()
409/// );
410/// // The user ID has to match when creating credentials!
411/// assert!(
412/// mapping
413/// .backend_user_with_passphrase("foo", passphrase.clone())
414/// .is_err()
415/// );
416///
417/// // Create new credentials with new passphrase based on backend user ID.
418/// assert!(
419/// mapping
420/// .backend_users_with_new_passphrase(BackendUserIdFilter {
421/// backend_user_id_kind: BackendUserIdKind::Backup
422/// })
423/// .first()
424/// .is_some_and(|creds| creds.user() == backend_id.to_string())
425/// );
426/// # Ok(())
427/// # }
428/// ```
429pub trait MappingBackendUserIds {
430 /// Returns a list of [`String`]s representing backend User IDs according to a `filter`.
431 fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String>;
432
433 /// Returns a specific [`UserWithPassphrase`] implementation for a backend user.
434 ///
435 /// # Errors
436 ///
437 /// Returns an error if `user` matches no backend user of the user mapping.
438 /// Note, that implementations may use [`Error::BackendUserIdMismatch`] for this, if they do not
439 /// wish to create their own error variant for this purpose.
440 fn backend_user_with_passphrase(
441 &self,
442 name: &str,
443 passphrase: Passphrase,
444 ) -> Result<Box<dyn UserWithPassphrase>, crate::Error>;
445
446 /// Returns a list of [`UserWithPassphrase`] implementations according to a `filter`.
447 ///
448 /// For each returned backend user a new [`Passphrase`] is generated using the default settings
449 /// of [`Passphrase::generate`].
450 ///
451 /// With an implementation of [`BackendUserIdFilter`] it is possible to target specific kinds of
452 /// backend users.
453 fn backend_users_with_new_passphrase(
454 &self,
455 filter: BackendUserIdFilter,
456 ) -> Vec<Box<dyn UserWithPassphrase>>;
457}
458
459/// An interface for returning an optional SSH `authorized_keys` entry.
460///
461/// # Example
462///
463/// ```
464/// use signstar_config::{AuthorizedKeyEntry, SystemUserId, config::MappingAuthorizedKeyEntry};
465/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
466///
467/// #[derive(Debug)]
468/// enum ExampleUserMapping {
469/// Admin {
470/// backend_id: u8,
471/// },
472/// Backup {
473/// backend_id: u8,
474/// ssh_authorized_key: AuthorizedKeyEntry,
475/// system_user: SystemUserId,
476/// },
477/// Metrics {
478/// backend_id: u8,
479/// ssh_authorized_key: AuthorizedKeyEntry,
480/// system_user: SystemUserId,
481/// },
482/// Signer {
483/// backend_id: u8,
484/// ssh_authorized_key: AuthorizedKeyEntry,
485/// system_user: SystemUserId,
486/// },
487/// }
488///
489/// impl MappingAuthorizedKeyEntry for ExampleUserMapping {
490/// fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
491/// match self {
492/// Self::Admin { .. } => None,
493/// Self::Backup {
494/// ssh_authorized_key, ..
495/// }
496/// | Self::Metrics {
497/// ssh_authorized_key, ..
498/// }
499/// | Self::Signer {
500/// ssh_authorized_key, ..
501/// } => Some(ssh_authorized_key),
502/// }
503/// }
504/// }
505///
506/// # fn main() -> testresult::TestResult {
507/// let ssh_authorized_key: AuthorizedKeyEntry = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?;
508/// let mapping = ExampleUserMapping::Backup{
509/// backend_id: 1,
510/// ssh_authorized_key: ssh_authorized_key.clone(),
511/// system_user: "backup".parse()?,
512/// };
513/// assert!(mapping.authorized_key_entry().is_some_and(|key| key == &ssh_authorized_key));
514/// # Ok(())
515/// # }
516/// ```
517pub trait MappingAuthorizedKeyEntry {
518 /// Returns an optional SSH `authorized_keys` entry.
519 ///
520 /// Implementations must return [`None`] if the specific mapping does not provide any
521 /// [`AuthorizedKeyEntry`].
522 fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry>;
523}
524
525/// An interface to define a generic filter when evaluating the key IDs of a backend.
526pub trait BackendKeyIdFilter: Clone {}
527
528/// An interface for returning a list of key IDs based on a filter.
529///
530/// The associated filter is an implementation of [`BackendKeyIdFilter`] and should target the
531/// inherent details of a cryptographic key in the backend.
532/// A key ID must only be returned, if the filter fully matches.
533///
534/// # Example
535///
536/// ```
537/// use signstar_config::{
538/// SystemUserId,
539/// config::{BackendKeyIdFilter, MappingBackendKeyId},
540/// };
541/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
542///
543/// #[derive(Clone, Debug, PartialEq)]
544/// enum KeyFeature {
545/// V1,
546/// V2,
547/// }
548///
549/// #[derive(Debug)]
550/// struct KeySetup {
551/// pub feature: KeyFeature,
552/// pub id: u8,
553/// }
554///
555/// #[derive(Debug)]
556/// enum ExampleUserMapping {
557/// Admin {
558/// backend_id: u8,
559/// },
560/// Backup {
561/// backend_id: u8,
562/// system_user: SystemUserId,
563/// },
564/// Metrics {
565/// backend_id: u8,
566/// system_user: SystemUserId,
567/// },
568/// Signer {
569/// backend_id: u8,
570/// key_setup: KeySetup,
571/// system_user: SystemUserId,
572/// },
573/// }
574///
575/// #[derive(Clone, Debug)]
576/// struct KeyFilter {
577/// pub feature: KeyFeature,
578/// }
579///
580/// impl BackendKeyIdFilter for KeyFilter {}
581///
582/// impl MappingBackendKeyId<KeyFilter> for ExampleUserMapping {
583/// fn backend_key_id(&self, filter: &KeyFilter) -> Option<String> {
584/// match self {
585/// Self::Admin { .. } | Self::Backup { .. } | Self::Metrics { .. } => None,
586/// Self::Signer { key_setup, .. } => {
587/// if key_setup.feature == filter.feature {
588/// Some(key_setup.id.to_string())
589/// } else {
590/// None
591/// }
592/// }
593/// }
594/// }
595/// }
596///
597/// # fn main() -> testresult::TestResult {
598/// let key_id = 1;
599/// let mapping = ExampleUserMapping::Signer {
600/// backend_id: 1,
601/// key_setup: KeySetup {
602/// feature: KeyFeature::V1,
603/// id: key_id,
604/// },
605/// system_user: "backup".parse()?,
606/// };
607///
608/// // A key ID is only returned, if the custom filter matches.
609/// assert!(
610/// mapping
611/// .backend_key_id(&KeyFilter {
612/// feature: KeyFeature::V1
613/// })
614/// .is_some_and(|id| id == key_id.to_string())
615/// );
616/// assert!(
617/// mapping
618/// .backend_key_id(&KeyFilter {
619/// feature: KeyFeature::V2
620/// })
621/// .is_none()
622/// );
623/// # Ok(())
624/// # }
625/// ```
626pub trait MappingBackendKeyId<T>
627where
628 T: BackendKeyIdFilter,
629{
630 /// Returns an optional [`String`] representing a backend key ID according to a `filter`.
631 fn backend_key_id(&self, filter: &T) -> Option<String>;
632}
633
634/// An interface to define a generic filter when evaluating the domains of a backend.
635pub trait BackendDomainFilter {}
636
637/// An interface for returning a backend domain based on an optional filter.
638///
639/// A backend domain usually describes a form of access restriction for a backend.
640/// With them restrictions to the access of cryptographic key material is enforced for backend
641/// users.
642///
643/// If a filter is provided, the domain ID must only be returned if the filter matches.
644///
645/// # Example
646///
647/// ```
648/// use signstar_config::{
649/// SystemUserId,
650/// config::{BackendDomainFilter, MappingBackendDomain},
651/// };
652/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
653///
654/// #[derive(Clone, Debug, PartialEq)]
655/// enum KeyFeature {
656/// V1,
657/// V2,
658/// }
659///
660/// #[derive(Debug)]
661/// struct KeySetup {
662/// pub feature: KeyFeature,
663/// pub id: u8,
664/// }
665///
666/// #[derive(Debug)]
667/// struct Domain {
668/// pub id: u8,
669/// pub special: bool,
670/// }
671///
672/// #[derive(Debug)]
673/// enum ExampleUserMapping {
674/// Admin {
675/// backend_id: u8,
676/// },
677/// Backup {
678/// backend_id: u8,
679/// system_user: SystemUserId,
680/// },
681/// Metrics {
682/// backend_id: u8,
683/// system_user: SystemUserId,
684/// },
685/// Signer {
686/// backend_id: u8,
687/// domain: Domain,
688/// key_setup: KeySetup,
689/// system_user: SystemUserId,
690/// },
691/// }
692///
693/// #[derive(Debug)]
694/// struct DomainFilter {
695/// pub special: bool,
696/// }
697///
698/// impl BackendDomainFilter for DomainFilter {}
699///
700/// impl MappingBackendDomain<DomainFilter> for ExampleUserMapping {
701/// fn backend_domain(&self, filter: Option<&DomainFilter>) -> Option<String> {
702/// match self {
703/// Self::Admin { .. } | Self::Backup { .. } | Self::Metrics { .. } => None,
704/// Self::Signer { domain, .. } => {
705/// if let Some(filter) = filter {
706/// if domain.special == filter.special {
707/// Some(domain.id.to_string())
708/// } else {
709/// None
710/// }
711/// } else {
712/// Some(domain.id.to_string())
713/// }
714/// }
715/// }
716/// }
717/// }
718///
719/// # fn main() -> testresult::TestResult {
720/// let domain_id = 1;
721/// let mapping = ExampleUserMapping::Signer {
722/// backend_id: 1,
723/// domain: Domain {
724/// id: domain_id,
725/// special: true,
726/// },
727/// key_setup: KeySetup {
728/// feature: KeyFeature::V1,
729/// id: 1,
730/// },
731/// system_user: "backup".parse()?,
732/// };
733///
734/// // A domain ID is only returned, if the custom filter matches.
735/// assert!(
736/// mapping
737/// .backend_domain(Some(&DomainFilter { special: true }))
738/// .is_some_and(|id| id == domain_id.to_string())
739/// );
740/// // .. or if no filter is provided and a backend user with a domain is found.
741/// assert!(
742/// mapping
743/// .backend_domain(Some(&DomainFilter { special: true }))
744/// .is_some_and(|id| id == domain_id.to_string())
745/// );
746/// assert!(
747/// mapping
748/// .backend_domain(Some(&DomainFilter { special: false }))
749/// .is_none()
750/// );
751/// # Ok(())
752/// # }
753/// ```
754pub trait MappingBackendDomain<T>
755where
756 T: BackendDomainFilter,
757{
758 /// Returns a [`String`] representing a backend domain according to an optional `filter`.
759 fn backend_domain(&self, filter: Option<&T>) -> Option<String>;
760}
761
762/// The kind of non-administrative backend user.
763///
764/// This distinguishes between the different access rights levels (i.e. backup, metrics, observer,
765/// signing) of a non-administrative backend user.
766#[derive(Clone, Copy, Debug, Default)]
767pub enum NonAdminBackendUserIdKind {
768 /// Any non-administrative user.
769 #[default]
770 Any,
771
772 /// Backup user.
773 Backup,
774
775 /// Metrics user.
776 Metrics,
777
778 /// User used to observe keys, without access to them.
779 Observer,
780
781 /// Signing user.
782 Signing,
783}
784
785impl From<NonAdminBackendUserIdKind> for BackendUserIdKind {
786 fn from(value: NonAdminBackendUserIdKind) -> Self {
787 match value {
788 NonAdminBackendUserIdKind::Any => Self::NonAdmin,
789 NonAdminBackendUserIdKind::Backup => Self::Backup,
790 NonAdminBackendUserIdKind::Metrics => Self::Metrics,
791 NonAdminBackendUserIdKind::Observer => Self::Observer,
792 NonAdminBackendUserIdKind::Signing => Self::Signing,
793 }
794 }
795}
796
797/// A filter for non-administrative user mapping variants.
798#[derive(Clone, Debug, Default)]
799pub struct NonAdminBackendUserIdFilter {
800 /// The kind of backend user.
801 pub backend_user_id_kind: NonAdminBackendUserIdKind,
802}
803
804impl From<NonAdminBackendUserIdFilter> for BackendUserIdFilter {
805 fn from(value: NonAdminBackendUserIdFilter) -> Self {
806 Self {
807 backend_user_id_kind: value.backend_user_id_kind.into(),
808 }
809 }
810}
811
812/// An interface to create and load secrets for backend users in user mapping implementations.
813///
814/// Implementations are required to also implement [`MappingBackendUserIds`] and
815/// [`MappingSystemUserId`]
816///
817/// # Example
818///
819/// ```
820/// use signstar_config::{
821/// Error,
822/// SystemUserId,
823/// config::{
824/// BackendUserIdFilter,
825/// BackendUserIdKind,
826/// MappingBackendUserIds,
827/// MappingBackendUserSecrets,
828/// MappingSystemUserId,
829/// },
830/// };
831/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
832///
833/// #[derive(Debug)]
834/// struct ExampleCreds {
835/// pub id: u8,
836/// pub passphrase: Passphrase,
837/// }
838///
839/// impl UserWithPassphrase for ExampleCreds {
840/// fn user(&self) -> String {
841/// self.id.to_string()
842/// }
843///
844/// fn passphrase(&self) -> &Passphrase {
845/// &self.passphrase
846/// }
847/// }
848///
849/// #[derive(Debug)]
850/// enum ExampleUserMapping {
851/// Admin {
852/// backend_id: u8,
853/// },
854/// Backup {
855/// backend_id: u8,
856/// system_user: SystemUserId,
857/// },
858/// Metrics {
859/// backend_id: u8,
860/// system_user: SystemUserId,
861/// },
862/// Signer {
863/// backend_id: u8,
864/// system_user: SystemUserId,
865/// },
866/// }
867///
868/// impl ExampleUserMapping {
869/// pub fn backend_user_id(&self) -> u8 {
870/// match self {
871/// Self::Admin { backend_id }
872/// | Self::Backup { backend_id, .. }
873/// | Self::Metrics { backend_id, .. }
874/// | Self::Signer { backend_id, .. } => *backend_id,
875/// }
876/// }
877/// }
878///
879/// impl MappingBackendUserIds for ExampleUserMapping {
880/// fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
881/// match self {
882/// Self::Admin { backend_id, .. } => {
883/// if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
884/// .contains(&filter.backend_user_id_kind)
885/// {
886/// return vec![backend_id.to_string()];
887/// }
888/// }
889/// Self::Backup { backend_id, .. } => {
890/// if [
891/// BackendUserIdKind::Any,
892/// BackendUserIdKind::Backup,
893/// BackendUserIdKind::NonAdmin,
894/// ]
895/// .contains(&filter.backend_user_id_kind)
896/// {
897/// return vec![backend_id.to_string()];
898/// }
899/// }
900/// Self::Metrics { backend_id, .. } => {
901/// if [
902/// BackendUserIdKind::Any,
903/// BackendUserIdKind::Metrics,
904/// BackendUserIdKind::NonAdmin,
905/// ]
906/// .contains(&filter.backend_user_id_kind)
907/// {
908/// return vec![backend_id.to_string()];
909/// }
910/// }
911/// Self::Signer { backend_id, .. } => {
912/// if [
913/// BackendUserIdKind::Any,
914/// BackendUserIdKind::Signing,
915/// BackendUserIdKind::NonAdmin,
916/// ]
917/// .contains(&filter.backend_user_id_kind)
918/// {
919/// return vec![backend_id.to_string()];
920/// }
921/// }
922/// }
923///
924/// Vec::new()
925/// }
926///
927/// fn backend_user_with_passphrase(
928/// &self,
929/// name: &str,
930/// passphrase: Passphrase,
931/// ) -> Result<Box<dyn UserWithPassphrase>, Error> {
932/// let backend_user_id = self.backend_user_id();
933/// if backend_user_id.to_string() != name {
934/// return Err(
935/// signstar_config::config::TraitsError::BackendUserIdMismatch {
936/// expected: name.to_string(),
937/// actual: backend_user_id.to_string(),
938/// }
939/// .into(),
940/// );
941/// }
942///
943/// Ok(Box::new(ExampleCreds {
944/// id: backend_user_id,
945/// passphrase,
946/// }))
947/// }
948///
949/// fn backend_users_with_new_passphrase(
950/// &self,
951/// filter: BackendUserIdFilter,
952/// ) -> Vec<Box<dyn UserWithPassphrase>> {
953/// if let Some(backend_id) = match self {
954/// Self::Admin { backend_id, .. } => {
955/// if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
956/// .contains(&filter.backend_user_id_kind)
957/// {
958/// Some(*backend_id)
959/// } else {
960/// None
961/// }
962/// }
963/// Self::Backup { backend_id, .. } => {
964/// if [
965/// BackendUserIdKind::Any,
966/// BackendUserIdKind::Backup,
967/// BackendUserIdKind::NonAdmin,
968/// ]
969/// .contains(&filter.backend_user_id_kind)
970/// {
971/// Some(*backend_id)
972/// } else {
973/// None
974/// }
975/// }
976/// Self::Metrics { backend_id, .. } => {
977/// if [
978/// BackendUserIdKind::Any,
979/// BackendUserIdKind::Metrics,
980/// BackendUserIdKind::NonAdmin,
981/// ]
982/// .contains(&filter.backend_user_id_kind)
983/// {
984/// Some(*backend_id)
985/// } else {
986/// None
987/// }
988/// }
989/// Self::Signer { backend_id, .. } => {
990/// if [
991/// BackendUserIdKind::Any,
992/// BackendUserIdKind::Signing,
993/// BackendUserIdKind::NonAdmin,
994/// ]
995/// .contains(&filter.backend_user_id_kind)
996/// {
997/// Some(*backend_id)
998/// } else {
999/// None
1000/// }
1001/// }
1002/// } {
1003/// vec![Box::new(ExampleCreds {
1004/// id: backend_id,
1005/// passphrase: Passphrase::generate(None),
1006/// })]
1007/// } else {
1008/// Vec::new()
1009/// }
1010/// }
1011/// }
1012///
1013/// impl MappingSystemUserId for ExampleUserMapping {
1014/// fn system_user_id(&self) -> Option<&SystemUserId> {
1015/// match self {
1016/// Self::Admin { .. } => None,
1017/// Self::Backup { system_user, .. }
1018/// | Self::Metrics { system_user, .. }
1019/// | Self::Signer { system_user, .. } => Some(system_user),
1020/// }
1021/// }
1022/// }
1023///
1024/// impl MappingBackendUserSecrets for ExampleUserMapping {}
1025/// ```
1026pub trait MappingBackendUserSecrets: MappingSystemUserId + MappingBackendUserIds {
1027 /// Creates on-disk secrets for non-administrative backend users of the mapping.
1028 ///
1029 /// Returns a list of the created credentials as [`UserWithPassphrase`] implementations.
1030 ///
1031 /// # Note
1032 ///
1033 /// Returns `Ok(None)`, if the mapping implementation tracks no system user.
1034 ///
1035 /// # Errors
1036 ///
1037 /// Returns an error if
1038 ///
1039 /// - the system user in the user mapping does not match an existing Unix user
1040 /// - the user calling this function is not root
1041 /// - [`write_passphrase_to_secrets_file`] fails for one of the newly generated passphrases
1042 fn create_non_admin_backend_user_secrets(
1043 &self,
1044 secret_handling: NonAdministrativeSecretHandling,
1045 ) -> Result<Option<Vec<Box<dyn UserWithPassphrase>>>, crate::Error> {
1046 let Some(user) = self.system_user_id_as_existing_unix_user()? else {
1047 // The mapping implementation does not track a system user.
1048 return Ok(None);
1049 };
1050
1051 // Get credentials for all non-admin backend users (with newly generated passphrases).
1052 let credentials = self.backend_users_with_new_passphrase(BackendUserIdFilter {
1053 backend_user_id_kind: BackendUserIdKind::NonAdmin,
1054 });
1055
1056 // Write the passphrase for each set of credentials to disk.
1057 for creds in credentials.iter() {
1058 write_passphrase_to_secrets_file(
1059 secret_handling,
1060 &user,
1061 &creds.user(),
1062 creds.passphrase(),
1063 )?
1064 }
1065
1066 Ok(Some(credentials))
1067 }
1068
1069 /// Loads secrets from on-disk files for each non-administrative backend user matching a
1070 /// `filter`.
1071 ///
1072 /// Returns a list of the loaded credentials as [`UserWithPassphrase`] implementations.
1073 ///
1074 /// # Notes
1075 ///
1076 /// Returns `Ok(None)`, if the mapping implementation tracks no system user.
1077 ///
1078 /// The system user of the user mapping implementation must match the effective user of the
1079 /// current process.
1080 ///
1081 /// Delegates to [`load_passphrase_from_secrets_file`] for the loading of a single
1082 /// [`Passphrase`] from a secrets file.
1083 ///
1084 /// # Errors
1085 ///
1086 /// Returns an error if
1087 ///
1088 /// - the system user in the user mapping does not match the currently calling Unix user
1089 /// - [`load_passphrase_from_secrets_file`] fails for one of the secret files of the system user
1090 /// of the mapping
1091 fn load_non_admin_backend_user_secrets(
1092 &self,
1093 secret_handling: NonAdministrativeSecretHandling,
1094 filter: NonAdminBackendUserIdFilter,
1095 ) -> Result<Option<Vec<Box<dyn UserWithPassphrase>>>, crate::Error> {
1096 let Some(system_user) = self.system_user_id_as_current_unix_user()? else {
1097 // The mapping implementation does not track a system user.
1098 return Ok(None);
1099 };
1100
1101 let mut credentials = Vec::new();
1102
1103 for backend_user in self.backend_user_ids(filter.into()) {
1104 credentials.push(self.backend_user_with_passphrase(
1105 &backend_user,
1106 load_passphrase_from_secrets_file(secret_handling, &system_user, &backend_user)?,
1107 )?);
1108 }
1109
1110 Ok(Some(credentials))
1111 }
1112}
1113
1114/// An interface for returning all [`SystemUserId`]s tracked by a configuration implementation.
1115///
1116/// # Example
1117///
1118/// ```
1119/// use std::collections::HashSet;
1120///
1121/// use signstar_config::{
1122/// SystemUserId,
1123/// config::{ConfigSystemUserIds, MappingSystemUserId},
1124/// };
1125/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
1126///
1127/// #[derive(Debug, Eq, Hash, PartialEq)]
1128/// enum ExampleUserMapping {
1129/// Admin {
1130/// backend_id: u8,
1131/// },
1132/// Backup {
1133/// backend_id: u8,
1134/// system_user: SystemUserId,
1135/// },
1136/// Metrics {
1137/// backend_id: u8,
1138/// system_user: SystemUserId,
1139/// },
1140/// Signer {
1141/// backend_id: u8,
1142/// system_user: SystemUserId,
1143/// },
1144/// }
1145///
1146/// impl ExampleUserMapping {
1147/// pub fn backend_user_id(&self) -> u8 {
1148/// match self {
1149/// Self::Admin { backend_id }
1150/// | Self::Backup { backend_id, .. }
1151/// | Self::Metrics { backend_id, .. }
1152/// | Self::Signer { backend_id, .. } => *backend_id,
1153/// }
1154/// }
1155/// }
1156///
1157/// impl MappingSystemUserId for ExampleUserMapping {
1158/// fn system_user_id(&self) -> Option<&SystemUserId> {
1159/// match self {
1160/// Self::Admin { .. } => None,
1161/// Self::Backup { system_user, .. }
1162/// | Self::Metrics { system_user, .. }
1163/// | Self::Signer { system_user, .. } => Some(system_user),
1164/// }
1165/// }
1166/// }
1167///
1168/// #[derive(Debug)]
1169/// struct Config {
1170/// pub mappings: HashSet<ExampleUserMapping>,
1171/// }
1172///
1173/// impl ConfigSystemUserIds for Config {
1174/// fn system_user_ids(&self) -> HashSet<&SystemUserId> {
1175/// self.mappings
1176/// .iter()
1177/// .filter_map(|mapping| mapping.system_user_id())
1178/// .collect::<HashSet<_>>()
1179/// }
1180/// }
1181///
1182/// # fn main() -> testresult::TestResult {
1183/// let config = Config {
1184/// mappings: HashSet::from_iter([
1185/// ExampleUserMapping::Admin { backend_id: 1 },
1186/// ExampleUserMapping::Backup {
1187/// backend_id: 2,
1188/// system_user: "backup".parse()?,
1189/// },
1190/// ExampleUserMapping::Metrics {
1191/// backend_id: 3,
1192/// system_user: "metrics".parse()?,
1193/// },
1194/// ExampleUserMapping::Signer {
1195/// backend_id: 3,
1196/// system_user: "signer".parse()?,
1197/// },
1198/// ]),
1199/// };
1200/// let system_users: HashSet<SystemUserId> =
1201/// HashSet::from_iter(["backup".parse()?, "metrics".parse()?, "signer".parse()?]);
1202///
1203/// assert_eq!(
1204/// config.system_user_ids(),
1205/// system_users.iter().collect::<HashSet<_>>()
1206/// );
1207/// # Ok(())
1208/// # }
1209/// ```
1210pub trait ConfigSystemUserIds {
1211 /// Returns the list of all [`SystemUserId`]s.
1212 fn system_user_ids(&self) -> HashSet<&SystemUserId>;
1213}
1214
1215/// An interface for returning all [`AuthorizedKeyEntry`]s tracked by a configuration
1216/// implementation.
1217///
1218/// # Example
1219///
1220/// ```
1221/// use std::collections::HashSet;
1222///
1223/// use signstar_config::{AuthorizedKeyEntry, SystemUserId, config::{ConfigAuthorizedKeyEntries, MappingAuthorizedKeyEntry}};
1224/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
1225///
1226/// #[derive(Debug, Eq, Hash, PartialEq)]
1227/// enum ExampleUserMapping {
1228/// Admin {
1229/// backend_id: u8,
1230/// },
1231/// Backup {
1232/// backend_id: u8,
1233/// ssh_authorized_key: AuthorizedKeyEntry,
1234/// system_user: SystemUserId,
1235/// },
1236/// Metrics {
1237/// backend_id: u8,
1238/// ssh_authorized_key: AuthorizedKeyEntry,
1239/// system_user: SystemUserId,
1240/// },
1241/// Signer {
1242/// backend_id: u8,
1243/// ssh_authorized_key: AuthorizedKeyEntry,
1244/// system_user: SystemUserId,
1245/// },
1246/// }
1247///
1248/// impl MappingAuthorizedKeyEntry for ExampleUserMapping {
1249/// fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
1250/// match self {
1251/// Self::Admin { .. } => None,
1252/// Self::Backup {
1253/// ssh_authorized_key, ..
1254/// }
1255/// | Self::Metrics {
1256/// ssh_authorized_key, ..
1257/// }
1258/// | Self::Signer {
1259/// ssh_authorized_key, ..
1260/// } => Some(ssh_authorized_key),
1261/// }
1262/// }
1263/// }
1264///
1265/// #[derive(Debug)]
1266/// struct Config {
1267/// pub mappings: HashSet<ExampleUserMapping>,
1268/// }
1269///
1270/// impl ConfigAuthorizedKeyEntries for Config {
1271/// fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
1272/// self.mappings
1273/// .iter()
1274/// .filter_map(|mapping| mapping.authorized_key_entry())
1275/// .collect::<HashSet<_>>()
1276/// }
1277/// }
1278///
1279/// # fn main() -> testresult::TestResult {
1280/// let config = Config {
1281/// mappings: HashSet::from_iter([
1282/// ExampleUserMapping::Admin {
1283/// backend_id: 1,
1284/// },
1285/// ExampleUserMapping::Backup {
1286/// backend_id: 2,
1287/// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1288/// system_user: "backup".parse()?,
1289/// },
1290/// ExampleUserMapping::Signer {
1291/// backend_id: 3,
1292/// ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1293/// system_user: "signer".parse()?,
1294/// },
1295/// ])
1296/// };
1297/// let ssh_authorized_keys: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
1298/// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1299/// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?
1300/// ]);
1301///
1302/// assert_eq!(config.authorized_key_entries(), ssh_authorized_keys.iter().collect::<HashSet<_>>());
1303/// # Ok(())
1304/// # }
1305/// ```
1306pub trait ConfigAuthorizedKeyEntries {
1307 /// Returns the list of all [`AuthorizedKeyEntry`]s.
1308 fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry>;
1309}
1310
1311#[cfg(test)]
1312mod tests {
1313 use rstest::rstest;
1314 use testresult::TestResult;
1315
1316 use super::*;
1317
1318 #[derive(Debug)]
1319 struct ExampleCreds {
1320 pub id: u8,
1321 pub passphrase: Passphrase,
1322 }
1323
1324 impl UserWithPassphrase for ExampleCreds {
1325 fn user(&self) -> String {
1326 self.id.to_string()
1327 }
1328
1329 fn passphrase(&self) -> &Passphrase {
1330 &self.passphrase
1331 }
1332 }
1333
1334 #[derive(Debug)]
1335 enum ExampleUserMapping {
1336 Admin { backend_id: u8 },
1337 Backup { backend_id: u8 },
1338 Metrics { backend_id: u8 },
1339 Observer { backend_id: u8 },
1340 Signer { backend_id: u8 },
1341 }
1342
1343 impl ExampleUserMapping {
1344 pub fn backend_user_id(&self) -> u8 {
1345 match self {
1346 Self::Admin { backend_id }
1347 | Self::Backup { backend_id }
1348 | Self::Metrics { backend_id }
1349 | Self::Observer { backend_id }
1350 | Self::Signer { backend_id } => *backend_id,
1351 }
1352 }
1353 }
1354
1355 impl MappingBackendUserIds for ExampleUserMapping {
1356 fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
1357 match self {
1358 Self::Admin { backend_id } => {
1359 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
1360 .contains(&filter.backend_user_id_kind)
1361 {
1362 return vec![backend_id.to_string()];
1363 }
1364 }
1365 Self::Backup { backend_id } => {
1366 if [
1367 BackendUserIdKind::Backup,
1368 BackendUserIdKind::NonAdmin,
1369 BackendUserIdKind::Any,
1370 ]
1371 .contains(&filter.backend_user_id_kind)
1372 {
1373 return vec![backend_id.to_string()];
1374 }
1375 }
1376 Self::Metrics { backend_id } => {
1377 if [
1378 BackendUserIdKind::Metrics,
1379 BackendUserIdKind::NonAdmin,
1380 BackendUserIdKind::Any,
1381 ]
1382 .contains(&filter.backend_user_id_kind)
1383 {
1384 return vec![backend_id.to_string()];
1385 }
1386 }
1387 Self::Observer { backend_id } => {
1388 if [
1389 BackendUserIdKind::Observer,
1390 BackendUserIdKind::NonAdmin,
1391 BackendUserIdKind::Any,
1392 ]
1393 .contains(&filter.backend_user_id_kind)
1394 {
1395 return vec![backend_id.to_string()];
1396 }
1397 }
1398 Self::Signer { backend_id } => {
1399 if [
1400 BackendUserIdKind::Signing,
1401 BackendUserIdKind::NonAdmin,
1402 BackendUserIdKind::Any,
1403 ]
1404 .contains(&filter.backend_user_id_kind)
1405 {
1406 return vec![backend_id.to_string()];
1407 }
1408 }
1409 }
1410
1411 Vec::new()
1412 }
1413
1414 fn backend_user_with_passphrase(
1415 &self,
1416 name: &str,
1417 passphrase: Passphrase,
1418 ) -> Result<Box<dyn UserWithPassphrase>, crate::Error> {
1419 let backend_user_id = self.backend_user_id();
1420 if backend_user_id.to_string() != name {
1421 return Err(Error::BackendUserIdMismatch {
1422 expected: name.to_string(),
1423 actual: backend_user_id.to_string(),
1424 }
1425 .into());
1426 }
1427
1428 Ok(Box::new(ExampleCreds {
1429 id: backend_user_id,
1430 passphrase,
1431 }))
1432 }
1433
1434 fn backend_users_with_new_passphrase(
1435 &self,
1436 filter: BackendUserIdFilter,
1437 ) -> Vec<Box<dyn UserWithPassphrase>> {
1438 if let Some(backend_id) = match self {
1439 Self::Admin { backend_id } => {
1440 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
1441 .contains(&filter.backend_user_id_kind)
1442 {
1443 Some(*backend_id)
1444 } else {
1445 None
1446 }
1447 }
1448 Self::Backup { backend_id } => {
1449 if [
1450 BackendUserIdKind::Backup,
1451 BackendUserIdKind::NonAdmin,
1452 BackendUserIdKind::Any,
1453 ]
1454 .contains(&filter.backend_user_id_kind)
1455 {
1456 Some(*backend_id)
1457 } else {
1458 None
1459 }
1460 }
1461 Self::Metrics { backend_id } => {
1462 if [
1463 BackendUserIdKind::Metrics,
1464 BackendUserIdKind::NonAdmin,
1465 BackendUserIdKind::Any,
1466 ]
1467 .contains(&filter.backend_user_id_kind)
1468 {
1469 Some(*backend_id)
1470 } else {
1471 None
1472 }
1473 }
1474 Self::Observer { backend_id } => {
1475 if [
1476 BackendUserIdKind::Observer,
1477 BackendUserIdKind::NonAdmin,
1478 BackendUserIdKind::Any,
1479 ]
1480 .contains(&filter.backend_user_id_kind)
1481 {
1482 Some(*backend_id)
1483 } else {
1484 None
1485 }
1486 }
1487 Self::Signer { backend_id } => {
1488 if [
1489 BackendUserIdKind::Signing,
1490 BackendUserIdKind::NonAdmin,
1491 BackendUserIdKind::Any,
1492 ]
1493 .contains(&filter.backend_user_id_kind)
1494 {
1495 Some(*backend_id)
1496 } else {
1497 None
1498 }
1499 }
1500 } {
1501 vec![Box::new(ExampleCreds {
1502 id: backend_id,
1503 passphrase: Passphrase::generate(None),
1504 })]
1505 } else {
1506 Vec::new()
1507 }
1508 }
1509 }
1510
1511 // NonAdminBackendUserIdFilter
1512 #[rstest]
1513 #[case(NonAdminBackendUserIdKind::Any, BackendUserIdKind::NonAdmin)]
1514 #[case(NonAdminBackendUserIdKind::Backup, BackendUserIdKind::Backup)]
1515 #[case(NonAdminBackendUserIdKind::Observer, BackendUserIdKind::Observer)]
1516 #[case(NonAdminBackendUserIdKind::Metrics, BackendUserIdKind::Metrics)]
1517 #[case(NonAdminBackendUserIdKind::Signing, BackendUserIdKind::Signing)]
1518 fn non_admin_backend_user_id_kind_to_backend_user_id_kind(
1519 #[case] input: NonAdminBackendUserIdKind,
1520 #[case] output: BackendUserIdKind,
1521 ) {
1522 let transform: BackendUserIdKind = input.into();
1523
1524 assert_eq!(transform, output)
1525 }
1526
1527 #[rstest]
1528 #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Any }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1529 #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Backup }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup })]
1530 #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Observer }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer })]
1531 #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Metrics }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics })]
1532 #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Signing }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing })]
1533 fn backend_user_id_filter_from_non_admin_backend_user_id_filter(
1534 #[case] input: NonAdminBackendUserIdFilter,
1535 #[case] output: BackendUserIdFilter,
1536 ) {
1537 let transform: BackendUserIdFilter = input.into();
1538
1539 assert_eq!(transform, output)
1540 }
1541
1542 #[rstest]
1543 #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1544 #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1545 #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1546 #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1547 #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1548 #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin })]
1549 #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup })]
1550 #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer })]
1551 #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics })]
1552 #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing })]
1553 #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1554 #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1555 #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1556 #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1557 fn backend_user_ids_matches(
1558 #[case] mapping: ExampleUserMapping,
1559 #[case] filter: BackendUserIdFilter,
1560 ) {
1561 assert_eq!(mapping.backend_user_ids(filter), ["1"])
1562 }
1563
1564 #[test]
1565 fn backend_user_with_passphrase_succeeds() -> TestResult {
1566 let mapping = ExampleUserMapping::Admin { backend_id: 1 };
1567 let passphrase = Passphrase::generate(None);
1568 let creds = mapping.backend_user_with_passphrase("1", passphrase.clone())?;
1569 assert_eq!(creds.user(), "1");
1570 assert_eq!(
1571 creds.passphrase().expose_borrowed(),
1572 passphrase.expose_borrowed()
1573 );
1574
1575 Ok(())
1576 }
1577
1578 #[test]
1579 fn backend_user_with_passphrase_fails() {
1580 let mapping = ExampleUserMapping::Admin { backend_id: 1 };
1581 assert!(
1582 mapping
1583 .backend_user_with_passphrase("2", Passphrase::generate(None))
1584 .is_err()
1585 );
1586 }
1587
1588 #[rstest]
1589 #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1590 #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1591 #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1592 #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1593 #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1594 #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin })]
1595 #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup })]
1596 #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer })]
1597 #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics })]
1598 #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing })]
1599 #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1600 #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1601 #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1602 #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1603 fn backend_users_with_new_passphrase_applies(
1604 #[case] mapping: ExampleUserMapping,
1605 #[case] filter: BackendUserIdFilter,
1606 ) {
1607 let creds = mapping.backend_users_with_new_passphrase(filter);
1608 assert!(creds.first().is_some_and(|creds| creds.user() == "1"))
1609 }
1610}