diff options
| author | tv <tv@krebsco.de> | 2026-11-02 22:46:48 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-02 22:46:48 +0100 |
| commit | e4331d13f313f439aff9cef1ea221c8adeeebb3a (patch) | |
| tree | 38648722d73ddf9b1deea6c3b05430994b82f4e6 /src/api/auth.rs | |
| parent | 4ae3326ebca32252020b5899e4a3566d0a6b5ec7 (diff) | |
api: allow listening to Unix sockets
Diffstat (limited to 'src/api/auth.rs')
| -rw-r--r-- | src/api/auth.rs | 47 |
1 files changed, 37 insertions, 10 deletions
diff --git a/src/api/auth.rs b/src/api/auth.rs index 48ec02d..400feed 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -1,6 +1,8 @@ use anyhow::Context; use argon2::{Argon2, password_hash::PasswordVerifier}; use axum::extract::{ConnectInfo, State}; + +use super::listen::Peer; use axum::{ body::Body, http::{ @@ -16,7 +18,7 @@ use std::{ collections::HashMap, fs::File, io::{BufRead, BufReader}, - net::{IpAddr, SocketAddr}, + net::IpAddr, sync::Arc, time::{Duration, Instant}, }; @@ -68,7 +70,32 @@ pub(crate) struct AuthState { /// A reverse proxy authenticates, and names the user in `header`. struct ProxyAuth { header: HeaderName, - trusted: Vec<Range>, + trusted: Vec<Trusted>, +} + +/// Where a proxy is believed from (`--trusted-proxies`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Trusted { + Addresses(Range), + /// The API's Unix sockets (`unix`). + Unix, +} + +impl Trusted { + fn parse(text: &str) -> anyhow::Result<Self> { + if text.trim() == "unix" { + return Ok(Self::Unix); + } + Range::parse(text).map(Self::Addresses) + } + + fn holds(&self, peer: Peer) -> bool { + match (self, peer) { + (Self::Addresses(range), Peer::Tcp(address)) => range.contains(address.ip()), + (Self::Unix, Peer::Unix) => true, + _ => false, + } + } } /// An address, or the addresses sharing its first `bits` bits. @@ -84,9 +111,9 @@ impl Range { Some((address, bits)) => (address, Some(bits)), None => (text.trim(), None), }; - let address: IpAddr = address - .parse() - .with_context(|| format!("trusted-proxies: {text:?} is no IP address"))?; + let address: IpAddr = address.parse().with_context(|| { + format!("trusted-proxies: {text:?} is neither an IP address nor unix") + })?; let all = if address.is_ipv4() { 32 } else { 128 }; let bits = match bits { None => all, @@ -140,7 +167,7 @@ impl AuthState { let trusted = settings .trusted_proxies .iter() - .map(|range| Range::parse(range)) + .map(|trusted| Trusted::parse(trusted)) .collect::<anyhow::Result<Vec<_>>>()?; Ok::<_, anyhow::Error>(Arc::new(ProxyAuth { header, trusted })) }) @@ -174,13 +201,13 @@ fn parse_basic(headers: &HeaderMap) -> Option<(String, String)> { pub(crate) struct AuthenticatedUser(pub(crate) String); /// The user the trusted proxy the request came from names, if it does. -fn proxied_user(state: &AuthState, headers: &HeaderMap, from: Option<IpAddr>) -> Option<String> { +fn proxied_user(state: &AuthState, headers: &HeaderMap, from: Option<Peer>) -> Option<String> { let proxy = state.proxy.as_ref()?; let user = headers.get(&proxy.header)?.to_str().ok()?.trim(); if user.is_empty() { return None; } - let trusted = from.is_some_and(|from| proxy.trusted.iter().any(|range| range.contains(from))); + let trusted = from.is_some_and(|from| proxy.trusted.iter().any(|trusted| trusted.holds(from))); if !trusted { tracing::debug!(from = ?from, header = %proxy.header, "User header from an address not in trusted-proxies; ignored"); return None; @@ -247,8 +274,8 @@ pub(crate) async fn basic_auth( ) -> Response { let from = request .extensions() - .get::<ConnectInfo<SocketAddr>>() - .map(|ConnectInfo(address)| address.ip()); + .get::<ConnectInfo<Peer>>() + .map(|ConnectInfo(peer)| *peer); let user = match proxied_user(&state, &headers, from) { Some(user) => Some(user), None => authenticate(&state, &headers).await, |
