signstar_config/
non_admin_credentials.rs

1//! Non-administrative credentials handling for an HSM backend.
2use std::{
3    fmt::{Debug, Display},
4    path::PathBuf,
5};
6
7use signstar_common::common::SECRET_FILE_MODE;
8use signstar_crypto::traits::UserWithPassphrase;
9
10use crate::{
11    ExtendedUserMapping,
12    SignstarConfig,
13    SystemUserId,
14    UserMapping,
15    utils::get_current_system_user,
16};
17
18/// An error that may occur when handling non-administrative credentials for an HSM backend.
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21    /// There are one or more errors when loading credentials for a specific system user.
22    #[error("Errors occurred when loading credentials for system user {system_user}:\n{errors}")]
23    CredentialsLoading {
24        /// The system user for which loading of backend user credentials led to errors.
25        system_user: SystemUserId,
26        /// The errors that occurred during loading of backend user credentials for `system_user`.
27        errors: CredentialsLoadingErrors,
28    },
29
30    /// There are no credentials for a specific system user.
31    #[error("There are no credentials for system user {system_user}")]
32    CredentialsMissing {
33        /// The system user for which credentials are missing.
34        system_user: SystemUserId,
35    },
36
37    /// A mapping does not offer a system user.
38    #[error("There is no system user in the mapping.")]
39    NoSystemUser,
40
41    /// A user is not a signing user for the HSM backend.
42    #[error("The user is not an operator user in the HSM backend used for signing.")]
43    NotSigningUser,
44
45    /// A passphrase directory can not be created.
46    #[error("Passphrase directory {path} for user {system_user} can not be created:\n{source}")]
47    SecretsDirCreate {
48        /// The path to a secrets directory that could not be created.
49        path: PathBuf,
50        /// The system user in whose home directory `path` could not be created.
51        system_user: SystemUserId,
52        /// The source error.
53        source: std::io::Error,
54    },
55
56    /// A secrets file can not be created.
57    #[error("The secrets file {path} can not be created for user {system_user}:\n{source}")]
58    SecretsFileCreate {
59        /// The path to a secrets file that could not be created.
60        path: PathBuf,
61        /// The system user in whose home directory `path` could not be created.
62        system_user: SystemUserId,
63        /// The source error.
64        source: std::io::Error,
65    },
66
67    /// The file metadata of a secrets file cannot be retrieved.
68    #[error("File metadata of secrets file {path} cannot be retrieved")]
69    SecretsFileMetadata {
70        /// The path to a secrets file for which metadata could not be retrieved.
71        path: PathBuf,
72        /// The source error.
73        source: std::io::Error,
74    },
75
76    /// A secrets file does not exist.
77    #[error("Secrets file not found: {path}")]
78    SecretsFileMissing {
79        /// The path to a secrets file that is missing.
80        path: PathBuf,
81    },
82
83    /// A secrets file is not a file.
84    #[error("Secrets file is not a file: {path}")]
85    SecretsFileNotAFile {
86        /// The path to a secrets file that is not a file.
87        path: PathBuf,
88    },
89
90    /// A secrets file does not have the correct permissions.
91    #[error("Secrets file {path} has permissions {mode}, but {SECRET_FILE_MODE} is required")]
92    SecretsFilePermissions {
93        /// The path to a secrets file for which permissions could not be set.
94        path: PathBuf,
95        /// The file mode that should be applied to the file at `path`.
96        mode: u32,
97    },
98
99    /// A secrets file cannot be read.
100    #[error("Failed reading secrets file {path}:\n{source}")]
101    SecretsFileRead {
102        /// The path to a secrets file that could not be read.
103        path: PathBuf,
104        /// The source error.
105        source: std::io::Error,
106    },
107
108    /// A secrets file can not be written to.
109    #[error("The secrets file {path} can not be written to for user {system_user}: {source}")]
110    SecretsFileWrite {
111        /// The path to a secrets file that could not be written to.
112        path: PathBuf,
113        /// The system user in whose home directory `path` resides.
114        system_user: SystemUserId,
115        /// The source error.
116        source: std::io::Error,
117    },
118}
119
120/// An error that may occur when loading credentials for a [`SystemUserId`].
121///
122/// Alongside an [`Error`][`crate::Error`] contains a target user name for which the error occurred.
123#[derive(Debug)]
124pub struct CredentialsLoadingError {
125    user_id: String,
126    error: crate::Error,
127}
128
129impl CredentialsLoadingError {
130    /// Creates a new [`CredentialsLoadingError`].
131    pub fn new(user_id: String, error: crate::Error) -> Self {
132        Self { user_id, error }
133    }
134
135    /// Returns a reference to the user name.
136    pub fn get_user_id(&self) -> &str {
137        &self.user_id
138    }
139
140    /// Returns a reference to the [`Error`][crate::Error].
141    pub fn get_error(&self) -> &crate::Error {
142        &self.error
143    }
144}
145
146impl Display for CredentialsLoadingError {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        write!(f, "{}: {}", self.user_id, self.error)
149    }
150}
151
152/// A wrapper for a list of [`CredentialsLoadingError`]s.
153#[derive(Debug)]
154pub struct CredentialsLoadingErrors {
155    errors: Vec<CredentialsLoadingError>,
156}
157
158impl CredentialsLoadingErrors {
159    /// Creates a new [`CredentialsLoadingError`].
160    pub fn new(errors: Vec<CredentialsLoadingError>) -> Self {
161        Self { errors }
162    }
163
164    /// Returns a reference to the list of [`CredentialsLoadingError`]s.
165    pub fn get_errors(&self) -> &[CredentialsLoadingError] {
166        &self.errors
167    }
168}
169
170impl Display for CredentialsLoadingErrors {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        write!(
173            f,
174            "{}",
175            self.errors
176                .iter()
177                .map(|error| error.to_string())
178                .collect::<Vec<String>>()
179                .join("\n")
180        )
181    }
182}
183
184/// A collection of credentials and credential loading errors for a system user.
185///
186/// Tracks a [`SystemUserId`], zero or more [`UserWithPassphrase`] implementations mapped to it, as
187/// well as zero or more errors related to loading the passphrase for each of those backend users.
188#[derive(Debug)]
189pub struct CredentialsLoading {
190    mapping: ExtendedUserMapping,
191    credentials: Vec<Box<dyn UserWithPassphrase>>,
192    errors: CredentialsLoadingErrors,
193}
194
195impl CredentialsLoading {
196    /// Creates a new [`CredentialsLoading`].
197    pub fn new(
198        mapping: ExtendedUserMapping,
199        credentials: Vec<Box<dyn UserWithPassphrase>>,
200        errors: CredentialsLoadingErrors,
201    ) -> Self {
202        Self {
203            mapping,
204            credentials,
205            errors,
206        }
207    }
208
209    /// Creates a [`CredentialsLoading`] for the calling system user.
210    ///
211    /// Uses the data of the calling system user to derive the specific mapping for it from the
212    /// Signstar configuration (a [`SignstarConfig`]).
213    /// Then continues to retrieve the credentials for all associated HSM users of the mapping.
214    ///
215    /// # Errors
216    ///
217    /// Returns an error if
218    /// - it is not possible to derive user data from the calling process,
219    /// - if there is no user data for the calling process,
220    /// - the Signstar configuration file does not exist,
221    /// - it is not possible to load the Signstar configuration,
222    /// - not exactly one user mapping exists for the calling system user,
223    /// - or if credentials loading fails due to a severe error.
224    pub fn from_system_user() -> Result<Self, crate::Error> {
225        let user = get_current_system_user()?;
226
227        let system_config = SignstarConfig::new_from_file(None)?;
228
229        let Some(mapping) = system_config.get_extended_mapping_for_user(&user.name) else {
230            return Err(
231                crate::ConfigError::NoMatchingMappingForSystemUser { name: user.name }.into(),
232            );
233        };
234
235        // get all credentials for the mapping
236        let credentials_loading = mapping.load_credentials()?;
237
238        Ok(credentials_loading)
239    }
240
241    /// Returns the [`ExtendedUserMapping`].
242    pub fn get_mapping(&self) -> &ExtendedUserMapping {
243        &self.mapping
244    }
245
246    /// Returns all [credentials][`UserWithPassphrase`].
247    pub fn get_credentials(&self) -> &[Box<dyn UserWithPassphrase>] {
248        &self.credentials
249    }
250
251    /// Returns a reference to a [`SystemUserId`].
252    ///
253    /// # Errors
254    ///
255    /// Returns an error if there is no system user in the tracked mapping.
256    pub fn get_system_user_id(&self) -> Result<&SystemUserId, crate::Error> {
257        match self.mapping.get_user_mapping().get_system_user() {
258            Some(system_user) => Ok(system_user),
259            None => Err(crate::Error::NonAdminSecretHandling(Error::NoSystemUser)),
260        }
261    }
262
263    /// Indicates whether there are any errors with user names.
264    ///
265    /// Returns `true` if there are errors, `false` otherwise.
266    pub fn has_userid_errors(&self) -> bool {
267        !self.errors.get_errors().is_empty()
268    }
269
270    /// Returns the collected errors for user names.
271    pub fn get_userid_errors(self) -> CredentialsLoadingErrors {
272        self.errors
273    }
274
275    /// Indicates whether the contained [`ExtendedUserMapping`] is that of a signing user.
276    pub fn has_signing_user(&self) -> bool {
277        match self.mapping.get_user_mapping() {
278            UserMapping::NetHsmOnlyAdmin(_)
279            | UserMapping::SystemNetHsmBackup { .. }
280            | UserMapping::SystemNetHsmMetrics { .. }
281            | UserMapping::HermeticSystemNetHsmMetrics { .. }
282            | UserMapping::SystemOnlyShareDownload { .. }
283            | UserMapping::SystemOnlyShareUpload { .. }
284            | UserMapping::SystemOnlyWireGuardDownload { .. } => false,
285            UserMapping::SystemNetHsmOperatorSigning { .. } => true,
286            #[cfg(feature = "yubihsm2")]
287            UserMapping::YubiHsmOnlyAdmin { .. } => false,
288            #[cfg(feature = "yubihsm2")]
289            UserMapping::SystemYubiHsmOperatorSigning { .. } => true,
290        }
291    }
292
293    /// Returns the credentials for a signing user.
294    ///
295    /// # Errors
296    ///
297    /// Returns an error if
298    /// - the tracked user is not a signing user
299    /// - errors occurred when loading the system user's credentials
300    /// - or there are no credentials for the system user.
301    pub fn credentials_for_signing_user(self) -> Result<Box<dyn UserWithPassphrase>, crate::Error> {
302        if !self.has_signing_user() {
303            return Err(crate::Error::NonAdminSecretHandling(Error::NotSigningUser));
304        }
305
306        if !self.errors.get_errors().is_empty() {
307            return Err(crate::Error::NonAdminSecretHandling(
308                Error::CredentialsLoading {
309                    system_user: self.get_system_user_id()?.clone(),
310                    errors: self.errors,
311                },
312            ));
313        }
314
315        let system_user = self.get_system_user_id()?.clone();
316        let mut iterator = self.credentials.into_iter();
317
318        if let Some(credentials) = iterator.next() {
319            Ok(credentials)
320        } else {
321            Err(crate::Error::NonAdminSecretHandling(
322                Error::CredentialsMissing { system_user },
323            ))
324        }
325    }
326}