Skip to main content

tuwunel_core/utils/
secret.rs

1//! Configuration-secret storage and resolution helpers.
2//!
3//! Secrets may come from files or inline configuration. The storage type does
4//! not redact output or erase memory when dropped.
5
6#[cfg(test)]
7mod tests;
8
9use std::{fs::read_to_string, path::Path};
10
11use crate::{error, smallstr::SmallString};
12
13/// Owned string used for secret configuration values.
14///
15/// The type provides 64 bytes of inline capacity and spills to the heap when
16/// needed. It does not redact formatting or erase its contents on drop.
17pub type Secret = SmallString<[u8; 64]>;
18
19/// Whether a secret is configured at all.
20///
21/// Answered without opening the file, so an unauthenticated caller cannot drive
22/// filesystem work. A configured file which turns out to be unreadable or blank
23/// still counts as set here, and only [`resolve`] discovers otherwise.
24#[must_use]
25pub fn is_set(file: Option<&Path>, inline: Option<&str>) -> bool {
26	file.is_some() || inline.is_some_and(|inline| !inline.is_empty())
27}
28
29/// Resolves a configured secret from a file or an inline value.
30///
31/// A successfully read file takes precedence and is trimmed; an empty file
32/// yields `None` instead of falling back inline. Read failures are logged and
33/// permit the inline value to be used, while empty values are discarded.
34#[must_use]
35pub fn resolve(file: Option<&Path>, inline: Option<&str>, name: &str) -> Option<Secret> {
36	let from_file = file.and_then(|path| {
37		read_to_string(path)
38			.inspect_err(|e| error!(%e, %name, "Failed to read secret file"))
39			.ok()
40	});
41
42	from_file
43		.as_deref()
44		.map(str::trim)
45		.or(inline)
46		.filter(|secret| !secret.is_empty())
47		.map(Secret::from)
48}