aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/auth.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-02 22:46:48 +0100
committertv <tv@krebsco.de>2026-11-02 22:46:48 +0100
commite4331d13f313f439aff9cef1ea221c8adeeebb3a (patch)
tree38648722d73ddf9b1deea6c3b05430994b82f4e6 /src/api/auth.rs
parent4ae3326ebca32252020b5899e4a3566d0a6b5ec7 (diff)
api: allow listening to Unix sockets
Diffstat (limited to 'src/api/auth.rs')
-rw-r--r--src/api/auth.rs47
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,