Compare commits

...

1 Commits

Author SHA1 Message Date
trinity-1686a 33d50666b5 wrap secrets in new type 2026-09-05 16:25:49 +02:00
10 changed files with 60 additions and 16 deletions
Generated
+1
View File
@@ -1890,6 +1890,7 @@ dependencies = [
"serde",
"serde_json",
"sha2 0.10.9",
"subtle",
"thiserror 2.0.18",
"tokio",
"toml",
+1
View File
@@ -76,6 +76,7 @@ pnet_datalink = "0.35"
rand = "0.9"
sha1 = "0.10"
sha2 = "0.10"
subtle = "2.6.1"
timeago = { version = "0.5", default-features = false }
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
+2 -2
View File
@@ -191,7 +191,7 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
.admin
.metrics_token
.as_ref()
.is_some_and(|s| s == &self.admin_token)
.is_some_and(|s| s.eq_ct(&self.admin_token))
{
return Ok(GetCurrentAdminTokenInfoResponse(
GetAdminTokenInfoResponse {
@@ -210,7 +210,7 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
.admin
.admin_token
.as_ref()
.is_some_and(|s| s == &self.admin_token)
.is_some_and(|s| s.eq_ct(&self.admin_token))
{
return Ok(GetCurrentAdminTokenInfoResponse(
GetAdminTokenInfoResponse {
+8 -2
View File
@@ -117,8 +117,14 @@ impl AdminApiServer {
#[cfg(feature = "metrics")] exporter: PrometheusExporter,
) -> Arc<Self> {
let cfg = &garage.config.admin;
let metrics_token = cfg.metrics_token.as_deref().map(hash_bearer_token);
let admin_token = cfg.admin_token.as_deref().map(hash_bearer_token);
let metrics_token = cfg
.metrics_token
.as_ref()
.map(|token| hash_bearer_token(token.extract_secret()));
let admin_token = cfg
.admin_token
.as_ref()
.map(|token| hash_bearer_token(token.extract_secret()));
let metrics_require_token = cfg.metrics_require_token;
let endpoint = garage.system.netapp.endpoint(ADMIN_RPC_PATH.into());
+2 -1
View File
@@ -307,7 +307,8 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
let net_key_hex_str = rpc_secret.ok_or("No RPC secret provided")?;
let network_key = NetworkKey::from_slice(
&hex::decode(&net_key_hex_str).err_context("Invalid RPC secret key (bad hex)")?[..],
&hex::decode(net_key_hex_str.extract_secret())
.err_context("Invalid RPC secret key (bad hex)")?[..],
)
.ok_or("Invalid RPC secret provided (wrong length)")?;
+8 -5
View File
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use structopt::StructOpt;
use garage_util::config::Config;
use garage_util::config::{Config, Secret};
use garage_util::error::Error;
/// Structure for secret values or paths that are passed as CLI arguments or environment
@@ -99,7 +99,7 @@ pub fn fill_secrets(mut config: Config, secrets: Secrets) -> Result<Config, Erro
}
pub(crate) fn fill_secret(
config_secret: &mut Option<String>,
config_secret: &mut Option<Secret<String>>,
config_secret_file: &Option<PathBuf>,
cli_secret: &Option<String>,
cli_secret_file: &Option<PathBuf>,
@@ -110,7 +110,7 @@ pub(crate) fn fill_secret(
(Some(_), Some(_)) => {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
}
(Some(secret), None) => Some(secret.to_string()),
(Some(secret), None) => Some(Secret::new(secret.to_string())),
(None, Some(file)) => Some(read_secret_file(file, allow_world_readable)?),
(None, None) => None,
};
@@ -132,7 +132,10 @@ pub(crate) fn fill_secret(
Ok(())
}
fn read_secret_file(file_path: &PathBuf, allow_world_readable: bool) -> Result<String, Error> {
fn read_secret_file(
file_path: &PathBuf,
allow_world_readable: bool,
) -> Result<Secret<String>, Error> {
if !allow_world_readable {
#[cfg(unix)]
{
@@ -152,7 +155,7 @@ fn read_secret_file(file_path: &PathBuf, allow_world_readable: bool) -> Result<S
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
Ok(String::from(secret_buf.trim_end()))
Ok(Secret::new(String::from(secret_buf.trim_end())))
}
#[cfg(test)]
+1 -1
View File
@@ -137,7 +137,7 @@ impl Garage {
info!("Initializing RPC...");
let network_key = hex::decode(config.rpc_secret.as_ref().ok_or_message(
"rpc_secret value is missing, not present in config file or in environment",
)?)
)?.extract_secret())
.ok()
.and_then(|x| NetworkKey::from_slice(&x))
.ok_or_message("Invalid RPC secret key: expected 32 bytes of random hex, please check the documentation for requirements")?;
+1 -1
View File
@@ -115,7 +115,7 @@ impl ConsulDiscovery {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
"x-consul-token",
reqwest::header::HeaderValue::from_str(token)?,
reqwest::header::HeaderValue::from_str(token.extract_secret())?,
);
builder = builder.default_headers(headers);
}
+1
View File
@@ -32,6 +32,7 @@ lazy_static.workspace = true
tracing.workspace = true
rand.workspace = true
sha2.workspace = true
subtle.workspace = true
chrono.workspace = true
rmp-serde.workspace = true
+35 -4
View File
@@ -90,7 +90,7 @@ pub struct Config {
pub allow_world_readable_secrets: bool,
/// RPC secret key: 32 bytes hex encoded
pub rpc_secret: Option<String>,
pub rpc_secret: Option<Secret<String>>,
/// Optional file where RPC secret key is read from
pub rpc_secret_file: Option<PathBuf>,
/// Address to bind for RPC
@@ -205,6 +205,37 @@ pub struct WebConfig {
pub add_host_to_metrics: bool,
}
#[derive(Deserialize, Clone)]
#[serde(transparent)]
pub struct Secret<T>(T);
impl<T> Secret<T> {
pub fn new(secret: T) -> Self {
Secret(secret)
}
pub fn extract_secret(&self) -> &T {
&self.0
}
}
impl<T: std::ops::Deref<Target = str>> Secret<T> {
pub fn eq_ct(&self, other: &T) -> bool {
use subtle::ConstantTimeEq;
self.0
.deref()
.as_bytes()
.ct_eq(other.deref().as_bytes())
.into()
}
}
impl<T> std::fmt::Debug for Secret<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Secret").finish_non_exhaustive()
}
}
/// Configuration for the admin and monitoring HTTP API
#[derive(Deserialize, Debug, Clone, Default)]
pub struct AdminConfig {
@@ -212,7 +243,7 @@ pub struct AdminConfig {
pub api_bind_addr: Option<UnixOrTCPSocketAddress>,
/// Bearer token to use to scrape metrics
pub metrics_token: Option<String>,
pub metrics_token: Option<Secret<String>>,
/// File to read metrics token from
pub metrics_token_file: Option<PathBuf>,
/// Whether to require an access token for accessing the metrics endpoint
@@ -220,7 +251,7 @@ pub struct AdminConfig {
pub metrics_require_token: bool,
/// Bearer token to use to access Admin API endpoints
pub admin_token: Option<String>,
pub admin_token: Option<Secret<String>>,
/// File to read admin token from
pub admin_token_file: Option<PathBuf>,
@@ -252,7 +283,7 @@ pub struct ConsulDiscoveryConfig {
/// Client TLS key to use when connecting to Consul
pub client_key: Option<String>,
/// /// Token to use for connecting to consul
pub token: Option<String>,
pub token: Option<Secret<String>>,
/// Skip TLS hostname verification
#[serde(default)]
pub tls_skip_verify: bool,