From f2b90bbcdb5347bfb68ee0e3343358e2a8e466dc Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 2 Nov 2026 21:34:12 +0100 Subject: api: allow auth delegation --- src/config/mod.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'src/config') diff --git a/src/config/mod.rs b/src/config/mod.rs index c7f96a3..37c010e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -100,14 +100,28 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ Kind::Text, "PATH", None, - "Users of the HTTP API (htpasswd file with Argon2 hashes). Required, but with relay-only (then without it, no HTTP API).", + "Users of the HTTP API (htpasswd file with Argon2 hashes). Required, but with user-header or relay-only (then without either, no HTTP API).", + ), + option( + "user-header", + Kind::Text, + "NAME", + None, + "A request header naming the user, set by a reverse proxy in front of the HTTP API that authenticated the request itself (e.g. X-Remote-User, from nginx's $remote_user). Believed only from trusted-proxies; other requests need htpasswd's basic authentication. The proxy must set or clear it on every request.", + ), + option( + "trusted-proxies", + Kind::List, + "ADDRESS,[ADDRESS...]", + Some("127.0.0.1,::1"), + "Addresses (an IP address, or a range as ADDRESS/BITS) of the reverse proxies whose user-header is believed.", ), option( "protocol-writers", Kind::List, "USER,[USER...]", None, - "HTTP API users (from htpasswd) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake. Such writes act on the whole swarm and are logged. Epoch metadata and the swarm ID are never writable.", + "HTTP API users (from htpasswd or user-header) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake. Such writes act on the whole swarm and are logged. Epoch metadata and the swarm ID are never writable.", ), option( "log-format", @@ -482,8 +496,11 @@ pub(crate) fn spec(name: &str) -> Option<&'static OptionSpec> { pub(crate) struct Settings { #[serde(default = "default_bind_addr")] pub(crate) bind_addr: SocketAddr, - /// Required (`check`), but with `relay_only`. + /// Required (`check`), but with `user_header` or `relay_only`. pub(crate) htpasswd: Option, + pub(crate) user_header: Option, + #[serde(default = "default_trusted_proxies")] + pub(crate) trusted_proxies: Vec, #[serde(default)] pub(crate) protocol_writers: Vec, #[serde(default)] @@ -621,6 +638,9 @@ pub(crate) struct Settings { fn default_bind_addr() -> SocketAddr { SocketAddr::from(([127, 0, 0, 1], 3000)) } +fn default_trusted_proxies() -> Vec { + vec!["127.0.0.1".to_owned(), "::1".to_owned()] +} fn default_document_append() -> Duration { Duration::from_secs(5) } @@ -696,8 +716,10 @@ impl Settings { if self.relay_only { self.check_relay_only()?; } else { - if self.htpasswd.is_none() { - bail!("htpasswd is required: the users of the HTTP API"); + if self.htpasswd.is_none() && self.user_header.is_none() { + bail!( + "htpasswd is required: the users of the HTTP API (or user-header, with a reverse proxy that authenticates them)" + ); } if self.node_key_file.is_none() { bail!( -- cgit v1.3.1