signstar_config/nethsm/admin_credentials.rs
1//! Administrative credentials for [`NetHsm`] backends.
2
3use nethsm::{FullCredentials, Passphrase};
4#[cfg(doc)]
5use nethsm::{NetHsm, UserId};
6use serde::{Deserialize, Serialize};
7
8use crate::{AdminCredentials, admin_credentials::Error};
9
10/// Administrative credentials.
11///
12/// Tracks the following credentials and passphrases:
13/// - the backup passphrase of the backend,
14/// - the unlock passphrase of the backend,
15/// - the top-level administrator credentials of the backend,
16/// - the namespace administrator credentials of the backend.
17///
18/// # Note
19///
20/// The unlock and backup passphrase must be at least 10 characters long.
21/// The passphrases of top-level and namespace administrator accounts must be at least 10 characters
22/// long.
23/// The list of top-level administrator credentials must include an account with the username
24/// "admin".
25#[derive(Clone, Debug, Default, Deserialize, Serialize)]
26pub struct NetHsmAdminCredentials {
27 iteration: u32,
28 backup_passphrase: Passphrase,
29 unlock_passphrase: Passphrase,
30 administrators: Vec<FullCredentials>,
31 namespace_administrators: Vec<FullCredentials>,
32}
33
34impl NetHsmAdminCredentials {
35 /// Creates a new [`NetHsmAdminCredentials`] instance.
36 ///
37 /// # Examples
38 ///
39 /// ```
40 /// use nethsm::FullCredentials;
41 /// use signstar_config::NetHsmAdminCredentials;
42 ///
43 /// # fn main() -> testresult::TestResult {
44 /// let creds = NetHsmAdminCredentials::new(
45 /// 1,
46 /// "backup-passphrase".parse()?,
47 /// "unlock-passphrase".parse()?,
48 /// vec![FullCredentials::new(
49 /// "admin".parse()?,
50 /// "admin-passphrase".parse()?,
51 /// )],
52 /// vec![FullCredentials::new(
53 /// "ns1~admin".parse()?,
54 /// "ns1-admin-passphrase".parse()?,
55 /// )],
56 /// )?;
57 /// # // the backup passphrase is too short
58 /// # assert!(NetHsmAdminCredentials::new(
59 /// # 1,
60 /// # "short".parse()?,
61 /// # "unlock-passphrase".parse()?,
62 /// # vec![FullCredentials::new("admin".parse()?, "admin-passphrase".parse()?)],
63 /// # vec![FullCredentials::new(
64 /// # "ns1~admin".parse()?,
65 /// # "ns1-admin-passphrase".parse()?,
66 /// # )],
67 /// # ).is_err());
68 /// #
69 /// # // the unlock passphrase is too short
70 /// # assert!(NetHsmAdminCredentials::new(
71 /// # 1,
72 /// # "backup-passphrase".parse()?,
73 /// # "short".parse()?,
74 /// # vec![FullCredentials::new("admin".parse()?, "admin-passphrase".parse()?)],
75 /// # vec![FullCredentials::new(
76 /// # "ns1~admin".parse()?,
77 /// # "ns1-admin-passphrase".parse()?,
78 /// # )],
79 /// # ).is_err());
80 /// #
81 /// # // there is no top-level administrator
82 /// # assert!(NetHsmAdminCredentials::new(
83 /// # 1,
84 /// # "backup-passphrase".parse()?,
85 /// # "unlock-passphrase".parse()?,
86 /// # Vec::new(),
87 /// # vec![FullCredentials::new(
88 /// # "ns1~admin".parse()?,
89 /// # "ns1-admin-passphrase".parse()?,
90 /// # )],
91 /// # ).is_err());
92 /// #
93 /// # // there is no default top-level administrator
94 /// # assert!(NetHsmAdminCredentials::new(
95 /// # 1,
96 /// # "backup-passphrase".parse()?,
97 /// # "unlock-passphrase".parse()?,
98 /// # vec![FullCredentials::new("some".parse()?, "admin-passphrase".parse()?)],
99 /// # vec![FullCredentials::new(
100 /// # "ns1~admin".parse()?,
101 /// # "ns1-admin-passphrase".parse()?,
102 /// # )],
103 /// # ).is_err());
104 /// #
105 /// # // a top-level administrator passphrase is too short
106 /// # assert!(NetHsmAdminCredentials::new(
107 /// # 1,
108 /// # "backup-passphrase".parse()?,
109 /// # "unlock-passphrase".parse()?,
110 /// # vec![FullCredentials::new("admin".parse()?, "short".parse()?)],
111 /// # vec![FullCredentials::new(
112 /// # "ns1~admin".parse()?,
113 /// # "ns1-admin-passphrase".parse()?,
114 /// # )],
115 /// # ).is_err());
116 /// #
117 /// # // a namespace administrator passphrase is too short
118 /// # assert!(NetHsmAdminCredentials::new(
119 /// # 1,
120 /// # "backup-passphrase".parse()?,
121 /// # "unlock-passphrase".parse()?,
122 /// # vec![FullCredentials::new("some".parse()?, "admin-passphrase".parse()?)],
123 /// # vec![FullCredentials::new(
124 /// # "ns1~admin".parse()?,
125 /// # "short".parse()?,
126 /// # )],
127 /// # ).is_err());
128 /// # Ok(())
129 /// # }
130 /// ```
131 pub fn new(
132 iteration: u32,
133 backup_passphrase: Passphrase,
134 unlock_passphrase: Passphrase,
135 administrators: Vec<FullCredentials>,
136 namespace_administrators: Vec<FullCredentials>,
137 ) -> Result<Self, crate::Error> {
138 let admin_credentials = Self {
139 iteration,
140 backup_passphrase,
141 unlock_passphrase,
142 administrators,
143 namespace_administrators,
144 };
145 admin_credentials.validate()?;
146
147 Ok(admin_credentials)
148 }
149
150 /// Returns the iteration.
151 pub fn get_iteration(&self) -> u32 {
152 self.iteration
153 }
154
155 /// Returns the backup passphrase.
156 pub fn get_backup_passphrase(&self) -> &str {
157 self.backup_passphrase.expose_borrowed()
158 }
159
160 /// Returns the unlock passphrase.
161 pub fn get_unlock_passphrase(&self) -> &str {
162 self.unlock_passphrase.expose_borrowed()
163 }
164
165 /// Returns the list of administrators.
166 pub fn get_administrators(&self) -> &[FullCredentials] {
167 &self.administrators
168 }
169
170 /// Returns the default system-wide administrator "admin".
171 ///
172 /// # Errors
173 ///
174 /// Returns an error if no administrative account with the system-wide [`UserId`] "admin" is
175 /// found.
176 pub fn get_default_administrator(&self) -> Result<&FullCredentials, crate::Error> {
177 let Some(first_admin) = self
178 .administrators
179 .iter()
180 .find(|user| user.name.to_string() == "admin")
181 else {
182 return Err(Error::AdministratorNoDefault.into());
183 };
184 Ok(first_admin)
185 }
186
187 /// Returns the list of namespace administrators.
188 pub fn get_namespace_administrators(&self) -> &[FullCredentials] {
189 &self.namespace_administrators
190 }
191}
192
193impl AdminCredentials for NetHsmAdminCredentials {
194 /// Validates the [`NetHsmAdminCredentials`].
195 ///
196 /// # Errors
197 ///
198 /// Returns an error if
199 /// - there is no top-level administrator user,
200 /// - the default top-level administrator user (with the name "admin") is missing,
201 /// - a user passphrase is too short,
202 /// - the backup passphrase is too short,
203 /// - or the unlock passphrase is too short.
204 fn validate(&self) -> Result<(), crate::Error> {
205 // there is no top-level administrator user
206 if self.get_administrators().is_empty() {
207 return Err(crate::Error::AdminSecretHandling(
208 Error::AdministratorMissing,
209 ));
210 }
211
212 // there is no top-level administrator user with the name "admin"
213 if !self
214 .get_administrators()
215 .iter()
216 .any(|user| user.name.to_string() == "admin")
217 {
218 return Err(crate::Error::AdminSecretHandling(
219 Error::AdministratorNoDefault,
220 ));
221 }
222
223 let minimum_length: usize = 10;
224
225 // a top-level administrator user passphrase is too short
226 for user in self.get_administrators().iter() {
227 if user.passphrase.expose_borrowed().len() < minimum_length {
228 return Err(crate::Error::AdminSecretHandling(
229 Error::PassphraseTooShort {
230 context: format!("user {}", user.name),
231 minimum_length,
232 },
233 ));
234 }
235 }
236
237 // a namespace administrator user passphrase is too short
238 for user in self.get_namespace_administrators().iter() {
239 if user.passphrase.expose_borrowed().len() < minimum_length {
240 return Err(crate::Error::AdminSecretHandling(
241 Error::PassphraseTooShort {
242 context: format!("user {}", user.name),
243 minimum_length,
244 },
245 ));
246 }
247 }
248
249 // the backup passphrase is too short
250 if self.get_backup_passphrase().len() < minimum_length {
251 return Err(crate::Error::AdminSecretHandling(
252 Error::PassphraseTooShort {
253 context: "backups".to_string(),
254 minimum_length,
255 },
256 ));
257 }
258
259 // the unlock passphrase is too short
260 if self.get_unlock_passphrase().len() < minimum_length {
261 return Err(crate::Error::AdminSecretHandling(
262 Error::PassphraseTooShort {
263 context: "unlocking".to_string(),
264 minimum_length,
265 },
266 ));
267 }
268
269 Ok(())
270 }
271}