secretfile/
lib.rs

1use std::borrow::Cow;
2use std::env::var;
3use std::error::Error;
4use std::fmt::Display;
5use std::fs::read_to_string;
6
7#[derive(Debug)]
8pub enum SecretError {
9    Load { path: String, error: std::io::Error },
10    MissingEnvVar(String),
11}
12
13impl Display for SecretError {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        match self {
16            SecretError::Load { path, error } => {
17                write!(f, "failed to load token from {path}: {error:#}")
18            }
19            SecretError::MissingEnvVar(var) => {
20                write!(f, "environment variable {var} referenced but not set")
21            }
22        }
23    }
24}
25
26impl Error for SecretError {}
27
28/// Load a secret from the provided path
29///
30/// If the provided path includes the `$CREDENTIALS_DIRECTORY` placeholder, it will be replaced with the
31/// systemd service credential directory.
32///
33/// any trailing whitespace will be stripped from the returned secret.
34pub fn load(path: &str) -> Result<String, SecretError> {
35    let file = if path.contains("$CREDENTIALS_DIRECTORY") {
36        let dir = var("CREDENTIALS_DIRECTORY")
37            .map_err(|_| SecretError::MissingEnvVar("$CREDENTIALS_DIRECTORY".into()))?;
38        Cow::Owned(path.replace("$CREDENTIALS_DIRECTORY", &dir))
39    } else {
40        Cow::Borrowed(path)
41    };
42
43    let mut content = read_to_string(file.as_ref()).map_err(|error| SecretError::Load {
44        path: file.into(),
45        error,
46    })?;
47
48    content.truncate(content.trim_end().len()); // trim in place
49    Ok(content)
50}