This commit is contained in:
Mateusz Gruszczyński committed 2026-10-19 11:09:48 +02:00
1 parent 4afa88a65e
commit 2ab44c4c62
49 files changed
+1523 -176

No files matched your search

+35 -31
View File
@@ -99,13 +99,9 @@ impl Settings {
"metrics: token environment variable must use A-Z, 0-9, underscore"
);
}
ensure!(
!self.enabled
|| self.listen.ip().is_loopback()
|| self.bearer_token_env.is_some()
|| self.allow_trusted_network_without_token,
"metrics: a non-loopback bind requires a bearer token or explicit trusted-network token bypass; protect HTTP with a private network/TLS tunnel"
);
// Runtime checks a generated credential (stored in SQLite) or the
// legacy environment variable. Without either, scrapes fail closed.
// Non-loopback HTTP still needs a private network/TLS tunnel.
Ok(())
}
pub fn restart_differs(&self, other: &Self) -> bool {
@@ -166,32 +162,40 @@ async fn scrape(State(exporter): State<Arc<Exporter>>, request: Request) -> Resp
if !allowed {
return StatusCode::FORBIDDEN.into_response();
}
if let Some(expected) = &exporter.token {
if settings.allow_trusted_network_without_token {
// The actual transport peer already matched an administrator-configured
// CIDR above. Forwarded headers never participate in this decision.
if !settings.allow_trusted_network_without_token {
let values: Vec<_> = request
.headers()
.get_all(http::header::AUTHORIZATION)
.iter()
.collect();
let provided = if values.len() == 1 {
values[0]
.to_str()
.ok()
.and_then(|s| s.strip_prefix("Bearer "))
} else {
let values: Vec<_> = request
.headers()
.get_all(http::header::AUTHORIZATION)
.iter()
.collect();
let valid = values.len() == 1
&& values[0]
.to_str()
.ok()
.and_then(|v| v.strip_prefix("Bearer "))
.is_some_and(|value| crate::auth::equal_secret(value, expected));
if !valid {
return (
StatusCode::UNAUTHORIZED,
[
("www-authenticate", "Bearer"),
("cache-control", "no-store"),
],
)
.into_response();
None
};
let generated = exporter.app.prometheus_token_hash.load_full();
let valid = provided.is_some_and(|value| {
if let Some(hash) = generated.as_deref() {
crate::auth::equal_secret(&crate::auth::secret_hash(value), hash)
} else {
exporter
.token
.as_ref()
.is_some_and(|token| crate::auth::equal_secret(value, token))
}
});
if !valid {
return (
StatusCode::UNAUTHORIZED,
[
("www-authenticate", "Bearer"),
("cache-control", "no-store"),
],
)
.into_response();
}
}
let Ok(_permit) = exporter.slots.try_acquire() else {
+3 -1
View File
@@ -8,7 +8,9 @@ mod tests {
settings.validate().unwrap();
settings.enabled = true;
settings.listen = "0.0.0.0:9191".parse().unwrap();
assert!(settings.validate().is_err());
// With generated tokens validation permits remote binds; without a
// configured credential the exporter itself returns 401, never 200.
assert!(settings.validate().is_ok());
settings.bearer_token_env = Some("RUST_PROXY_METRICS_TOKEN".into());
settings.validate().unwrap();
settings.bearer_token_env = None;