1use std::{
3 fs::{Permissions, create_dir_all, read_dir, set_permissions, write},
4 os::{linux::fs::MetadataExt, unix::fs::PermissionsExt},
5 path::{Path, PathBuf},
6 process::{Child, Command},
7 thread,
8 time,
9};
10
11use log::debug;
12use nethsm::{FullCredentials, Passphrase, UserId};
13use rand::{Rng, distributions::Alphanumeric, thread_rng};
14use signstar_common::config::get_default_config_file_path;
15use tempfile::NamedTempFile;
16use which::which;
17
18use crate::{
19 AdminCredentials,
20 AdministrativeSecretHandling,
21 ExtendedUserMapping,
22 NetHsmAdminCredentials,
23 SignstarConfig,
24};
25
26#[derive(Debug, thiserror::Error)]
28pub enum Error {
29 #[error("Unable to apply permissions to {path}:\n{source}")]
31 ApplyPermissions {
32 path: PathBuf,
34
35 source: std::io::Error,
37 },
38
39 #[error("Unable to create directory {dir}:\n{source}")]
41 CreateDirectory {
42 dir: PathBuf,
44
45 source: std::io::Error,
47 },
48
49 #[error("Unable to start socket for io.systemd.Credentials:\n{0}")]
51 CredentialsSocket(#[source] std::io::Error),
52
53 #[error("I/O error while {context}:\n{source}")]
55 Io {
56 context: &'static str,
58
59 source: std::io::Error,
61 },
62
63 #[error("I/O error at {path} while {context}:\n{source}")]
65 IoPath {
66 path: PathBuf,
68
69 context: &'static str,
71
72 source: std::io::Error,
74 },
75
76 #[error("Signstar-config error:\n{0}")]
78 SignstarConfig(#[from] crate::Error),
79
80 #[error("Timeout of {timeout}ms reached while {context}")]
82 Timeout {
83 timeout: u64,
85
86 context: String,
88 },
89
90 #[error("A temporary file for {purpose} cannot be created:\n{source}")]
92 Tmpfile {
93 purpose: &'static str,
95
96 source: std::io::Error,
98 },
99}
100
101pub fn list_files_in_dir(path: impl AsRef<Path>) -> Result<(), Error> {
103 let path = path.as_ref();
104 let entries = read_dir(path).map_err(|source| Error::IoPath {
105 path: path.to_path_buf(),
106 context: "reading its children",
107 source,
108 })?;
109
110 for entry in entries {
111 let entry = entry.map_err(|source| Error::IoPath {
112 path: path.to_path_buf(),
113 context: "getting an entry below it",
114 source,
115 })?;
116 let meta = entry.metadata().map_err(|source| Error::IoPath {
117 path: path.to_path_buf(),
118 context: "getting metadata",
119 source,
120 })?;
121
122 debug!(
123 "{} {}/{} {entry:?}",
124 meta.permissions().mode(),
125 meta.st_uid(),
126 meta.st_gid()
127 );
128
129 if meta.is_dir() {
130 list_files_in_dir(entry.path())?;
131 }
132 }
133
134 Ok(())
135}
136
137pub fn get_tmp_config(data: &[u8]) -> Result<NamedTempFile, Error> {
139 let tmp_config = NamedTempFile::new().map_err(|source| Error::Tmpfile {
140 purpose: "full signstar configuration",
141 source,
142 })?;
143 write(&tmp_config, data).map_err(|source| Error::Io {
144 context: "writing full signstar configuration to temporary file",
145 source,
146 })?;
147 Ok(tmp_config)
148}
149
150pub fn write_machine_id() -> Result<(), Error> {
159 debug!("Write dummy /etc/machine-id, required for systemd-creds");
160 let machine_id = PathBuf::from("/etc/machine-id");
161 std::fs::write(&machine_id, "d3b07384d113edec49eaa6238ad5ff00").map_err(|source| {
162 Error::IoPath {
163 path: machine_id.to_path_buf(),
164 context: "writing machine-id",
165 source,
166 }
167 })?;
168
169 let metadata = machine_id.metadata().map_err(|source| Error::IoPath {
170 path: machine_id,
171 context: "getting metadata of file",
172 source,
173 })?;
174 debug!(
175 "/etc/machine-id\nmode: {}\nuid: {}\ngid: {}",
176 metadata.permissions().mode(),
177 metadata.st_uid(),
178 metadata.st_gid()
179 );
180 Ok(())
181}
182
183#[derive(Debug)]
188pub struct BackgroundProcess {
189 child: Child,
190 command: String,
191}
192
193impl BackgroundProcess {
194 pub fn kill(&mut self) -> Result<(), Error> {
200 self.child.kill().map_err(|source| Error::Io {
201 context: "killing process",
202 source,
203 })
204 }
205}
206
207impl Drop for BackgroundProcess {
208 fn drop(&mut self) {
210 if let Err(error) = self.child.kill() {
211 log::debug!(
212 "Unable to kill background process of command {}:\n{error}",
213 self.command
214 )
215 }
216 }
217}
218
219pub fn start_credentials_socket() -> Result<BackgroundProcess, Error> {
232 let systemd_run_path = PathBuf::from("/run/systemd");
233 let socket_path = PathBuf::from("/run/systemd/io.systemd.Credentials");
234 create_dir_all(&systemd_run_path).map_err(|source| Error::CreateDirectory {
235 dir: systemd_run_path,
236 source,
237 })?;
238
239 let command = "systemd-socket-activate";
241 let systemd_socket_activate = which(command).map_err(|source| {
242 Error::SignstarConfig(
243 crate::utils::Error::ExecutableNotFound {
244 command: command.to_string(),
245 source,
246 }
247 .into(),
248 )
249 })?;
250 let mut command = Command::new(systemd_socket_activate);
251 let command = command.args([
252 "--listen",
253 "/run/systemd/io.systemd.Credentials",
254 "--accept",
255 "--fdname=varlink",
256 "systemd-creds",
257 ]);
258 let child = command.spawn().map_err(Error::CredentialsSocket)?;
259
260 let timeout = 10000;
262 let step = 100;
263 let mut elapsed = 0;
264 let mut permissions_set = false;
265 while elapsed < timeout {
266 if socket_path.exists() {
267 debug!("Found {socket_path:?}");
268 set_permissions(socket_path.as_path(), Permissions::from_mode(0o666)).map_err(
269 |source| Error::ApplyPermissions {
270 path: socket_path.to_path_buf(),
271 source,
272 },
273 )?;
274 permissions_set = true;
275 break;
276 } else {
277 thread::sleep(time::Duration::from_millis(step));
278 elapsed += step;
279 }
280 }
281 if !permissions_set {
282 return Err(Error::Timeout {
283 timeout,
284 context: format!("waiting for {socket_path:?}"),
285 });
286 }
287
288 Ok(BackgroundProcess {
289 child,
290 command: format!("{command:?}"),
291 })
292}
293
294pub fn prepare_system_with_config(
315 config_data: &[u8],
316) -> Result<(Vec<ExtendedUserMapping>, BackgroundProcess), Error> {
317 write_machine_id()?;
318
319 let config = SignstarConfig::new_from_file(Some(get_tmp_config(config_data)?.path()))?;
321
322 config.store(Some(&get_default_config_file_path()))?;
324
325 let creds_mapping: Vec<ExtendedUserMapping> = config.into();
327
328 Ok((creds_mapping, start_credentials_socket()?))
331}
332
333pub fn admin_credentials(config_data: &[u8]) -> Result<NetHsmAdminCredentials, Error> {
344 let config_file = get_tmp_config(config_data)?;
345 NetHsmAdminCredentials::load_from_file(
346 config_file.path(),
347 AdministrativeSecretHandling::Plaintext,
348 )
349 .map_err(Error::SignstarConfig)
350}
351
352pub fn signstar_config(config_data: &[u8]) -> Result<SignstarConfig, Error> {
363 SignstarConfig::new_from_file(Some(get_tmp_config(config_data)?.path()))
364 .map_err(Error::SignstarConfig)
365}
366
367pub fn create_full_credentials(users: &[UserId]) -> Vec<FullCredentials> {
372 fn create_passphrase() -> String {
374 thread_rng()
375 .sample_iter(&Alphanumeric)
376 .take(30)
377 .map(char::from)
378 .collect()
379 }
380
381 users
382 .iter()
383 .map(|user| FullCredentials::new(user.clone(), Passphrase::new(create_passphrase())))
384 .collect()
385}