signstar_crypto/secret_file/
common.rs

1//! Common functionality for the creation and loading of secrets from files.
2
3use std::{os::unix::fs::PermissionsExt, path::Path};
4
5use signstar_common::common::SECRET_FILE_MODE;
6
7/// Checks the accessibility of a secrets file.
8///
9/// Checks whether file at `path`
10///
11/// - exists,
12/// - is a file,
13/// - has accessible metadata,
14/// - and has the file mode [`SECRET_FILE_MODE`].
15///
16/// # Errors
17///
18/// Returns an error, if the file at `path`
19///
20/// - does not exist,
21/// - is not a file,
22/// - does not have accessible metadata,
23/// - or has a file mode other than [`SECRET_FILE_MODE`].
24pub(crate) fn check_secrets_file(path: impl AsRef<Path>) -> Result<(), crate::Error> {
25    let path = path.as_ref();
26
27    // check if a path exists
28    if !path.exists() {
29        return Err(crate::secret_file::Error::SecretsFileMissing {
30            path: path.to_path_buf(),
31        }
32        .into());
33    }
34
35    // check if this is a file
36    if !path.is_file() {
37        return Err(crate::secret_file::Error::SecretsFileNotAFile {
38            path: path.to_path_buf(),
39        }
40        .into());
41    }
42
43    // check for correct permissions
44    match path.metadata() {
45        Ok(metadata) => {
46            let mode = metadata.permissions().mode();
47            if mode != SECRET_FILE_MODE {
48                return Err(crate::secret_file::Error::SecretsFilePermissions {
49                    path: path.to_path_buf(),
50                    mode,
51                }
52                .into());
53            }
54        }
55        Err(source) => {
56            return Err(crate::secret_file::Error::SecretsFileMetadata {
57                path: path.to_path_buf(),
58                source,
59            }
60            .into());
61        }
62    }
63
64    Ok(())
65}
66
67#[cfg(test)]
68mod tests {
69    use std::fs::{Permissions, set_permissions};
70
71    use log::{LevelFilter, debug};
72    use signstar_common::logging::setup_logging;
73    use tempfile::{NamedTempFile, TempDir};
74    use testresult::TestResult;
75
76    use super::*;
77
78    /// Ensures that a file with the correct permissions is successfully checked using
79    /// [`check_secrets_file`].
80    #[test]
81    fn check_secrets_file_succeeds() -> TestResult {
82        setup_logging(LevelFilter::Debug)?;
83
84        let temp_file = NamedTempFile::new()?;
85        let path = temp_file.path();
86        set_permissions(path, Permissions::from_mode(SECRET_FILE_MODE))?;
87        debug!(
88            "Created {path:?} with mode {:o}",
89            path.metadata()?.permissions().mode()
90        );
91
92        check_secrets_file(path)?;
93
94        Ok(())
95    }
96
97    /// Ensures that passing a non-existent file to [`check_secrets_file`] fails.
98    #[test]
99    fn check_secrets_file_fails_on_missing_file() -> TestResult {
100        setup_logging(LevelFilter::Debug)?;
101
102        let temp_file = NamedTempFile::new()?;
103        let path = temp_file.path().to_path_buf();
104        temp_file.close()?;
105
106        if check_secrets_file(&path).is_ok() {
107            panic!("The path {path:?} is missing and should not have passed as a secrets file.");
108        }
109
110        Ok(())
111    }
112
113    /// Ensures that passing a directory to [`check_secrets_file`] fails.
114    #[test]
115    fn check_secrets_file_fails_on_dir() -> TestResult {
116        setup_logging(LevelFilter::Debug)?;
117
118        let temp_file = TempDir::new()?;
119        let path = temp_file.path();
120        debug!(
121            "Created {path:?} with mode {:o}",
122            path.metadata()?.permissions().mode()
123        );
124
125        if check_secrets_file(path).is_ok() {
126            panic!("The dir {path:?} should not have passed as a secrets file.");
127        }
128
129        Ok(())
130    }
131
132    /// Ensures that a file without the correct permissions fails [`check_secrets_file`].
133    #[test]
134    fn check_secrets_file_fails_on_invalid_permissions() -> TestResult {
135        setup_logging(LevelFilter::Debug)?;
136
137        let temp_file = NamedTempFile::new()?;
138        let path = temp_file.path();
139        set_permissions(path, Permissions::from_mode(0o100644))?;
140        debug!(
141            "Created {path:?} with mode {:o}",
142            path.metadata()?.permissions().mode()
143        );
144
145        if check_secrets_file(path).is_ok() {
146            panic!("The file at {path:?} should not have passed as a secrets file.");
147        }
148
149        Ok(())
150    }
151}