support treating own effective uid as privileged

I intend this to be an easy bootstrapping mechanism for web auth.
This commit is contained in:
Scott Lamb 2022-03-11 11:10:26 -08:00
parent 4ce3e511b5
commit 7c453b5f9d
4 changed files with 79 additions and 26 deletions

View File

@ -67,6 +67,11 @@ pub struct BindConfig {
/// specify a localhost bind address.
#[serde(default)]
pub trust_forward_hdrs: bool,
/// On Unix-domain sockets, treat clients with the Moonfire NVR server's own
/// effective UID as privileged.
#[serde(default)]
pub own_uid_is_privileged: bool,
}
#[derive(Debug, Deserialize)]

View File

@ -336,6 +336,7 @@ async fn inner(
};
// Start the web interface(s).
let own_euid = nix::unistd::Uid::effective();
let web_handles: Result<Vec<_>, Error> = config
.binds
.iter()
@ -349,11 +350,13 @@ async fn inner(
.map(Permissions::as_proto),
trust_forward_hdrs: b.trust_forward_hdrs,
time_zone_name: time_zone_name.clone(),
privileged_unix_uid: b.own_uid_is_privileged.then(|| own_euid),
})?);
let make_svc = make_service_fn(move |_conn| {
let make_svc = make_service_fn(move |conn: &crate::web::accept::Conn| {
let conn_data = *conn.data();
futures::future::ok::<_, std::convert::Infallible>(service_fn({
let svc = Arc::clone(&svc);
move |req| Arc::clone(&svc).serve(req)
move |req| Arc::clone(&svc).serve(req, conn_data)
}))
});
let listener = make_listener(&b.address)?;

View File

@ -28,8 +28,10 @@ impl Accept for Listener {
}
Some(Ok(Conn {
stream: Stream::Tcp(s),
client_unix_uid: None,
client_addr: Some(a),
data: ConnData {
client_unix_uid: None,
client_addr: Some(a),
},
}))
}),
Listener::Unix(l) => Pin::new(l).poll_accept(cx)?.map(|(s, _a)| {
@ -39,8 +41,10 @@ impl Accept for Listener {
};
Some(Ok(Conn {
stream: Stream::Unix(s),
client_unix_uid: Some(ucred.uid()),
client_addr: None,
data: ConnData {
client_unix_uid: Some(nix::unistd::Uid::from_raw(ucred.uid())),
client_addr: None,
},
}))
}),
}
@ -50,19 +54,19 @@ impl Accept for Listener {
/// An open connection.
pub struct Conn {
stream: Stream,
client_unix_uid: Option<libc::uid_t>,
client_addr: Option<std::net::SocketAddr>,
data: ConnData,
}
/// Extra data associated with a connection.
#[derive(Copy, Clone)]
pub struct ConnData {
pub client_unix_uid: Option<nix::unistd::Uid>,
pub client_addr: Option<std::net::SocketAddr>,
}
impl Conn {
#[allow(dead_code)] // TODO: feed this onward.
pub fn client_unix_uid(&self) -> Option<libc::uid_t> {
self.client_unix_uid
}
#[allow(dead_code)] // TODO: feed this onward.
pub fn client_addr(&self) -> Option<&std::net::SocketAddr> {
self.client_addr.as_ref()
pub fn data(&self) -> &ConnData {
&self.data
}
}

View File

@ -10,6 +10,7 @@ mod signals;
mod static_file;
mod view;
use self::accept::ConnData;
use self::path::Path;
use crate::body::Body;
use crate::json;
@ -88,6 +89,7 @@ fn from_base_error(err: base::Error) -> Response<Body> {
plain_response(status_code, err.to_string())
}
#[derive(Debug)]
struct Caller {
permissions: db::Permissions,
user: Option<json::ToplevelUser>,
@ -160,6 +162,7 @@ pub struct Config<'a> {
pub trust_forward_hdrs: bool,
pub time_zone_name: String,
pub allow_unauthenticated_permissions: Option<db::Permissions>,
pub privileged_unix_uid: Option<nix::unistd::Uid>,
}
pub struct Service {
@ -169,6 +172,7 @@ pub struct Service {
time_zone_name: String,
allow_unauthenticated_permissions: Option<db::Permissions>,
trust_forward_hdrs: bool,
privileged_unix_uid: Option<nix::unistd::Uid>,
}
/// Useful HTTP `Cache-Control` values to set on successful (HTTP 200) API responses.
@ -221,6 +225,7 @@ impl Service {
allow_unauthenticated_permissions: config.allow_unauthenticated_permissions,
trust_forward_hdrs: config.trust_forward_hdrs,
time_zone_name: config.time_zone_name,
privileged_unix_uid: config.privileged_unix_uid,
})
}
@ -240,7 +245,7 @@ impl Service {
self.init_segment(sha1, debug, &req)?,
),
Path::TopLevel => (CacheControl::PrivateDynamic, self.top_level(&req, caller)?),
Path::Request => (CacheControl::PrivateDynamic, self.request(&req)?),
Path::Request => (CacheControl::PrivateDynamic, self.request(&req, caller)?),
Path::Camera(uuid) => (CacheControl::PrivateDynamic, self.camera(&req, uuid)?),
Path::StreamRecordings(uuid, type_) => (
CacheControl::PrivateDynamic,
@ -298,6 +303,7 @@ impl Service {
pub async fn serve(
self: Arc<Self>,
req: Request<::hyper::Body>,
conn_data: ConnData,
) -> Result<Response<Body>, std::convert::Infallible> {
let p = Path::decode(req.uri().path());
let always_allow_unauthenticated = matches!(
@ -305,7 +311,7 @@ impl Service {
Path::NotFound | Path::Request | Path::Login | Path::Logout | Path::Static
);
debug!("request on: {}: {:?}", req.uri(), p);
let caller = match self.authenticate(&req, always_allow_unauthenticated) {
let caller = match self.authenticate(&req, &conn_data, always_allow_unauthenticated) {
Ok(c) => c,
Err(e) => return Ok(from_base_error(e)),
};
@ -506,7 +512,7 @@ impl Service {
}
}
fn request(&self, req: &Request<::hyper::Body>) -> ResponseResult {
fn request(&self, req: &Request<::hyper::Body>, caller: Caller) -> ResponseResult {
let authreq = self.authreq(req);
let host = req
.headers()
@ -523,7 +529,8 @@ impl Service {
host: {:?}\n\
addr: {:?}\n\
user_agent: {:?}\n\
secure: {:?}",
secure: {:?}\n\
caller: {:?}\n",
time::at(time::Timespec {
sec: authreq.when_sec.unwrap(),
nsec: 0
@ -534,7 +541,8 @@ impl Service {
host.as_deref(),
&authreq.addr,
agent.as_deref(),
self.is_secure(req)
self.is_secure(req),
&caller,
),
))
}
@ -555,11 +563,13 @@ impl Service {
/// Authenticates the session (if any) and returns a Caller.
///
/// If there's no session,
/// 1. if `allow_unauthenticated_permissions` is configured, returns okay
/// 1. if connected via Unix domain socket from the same effective uid
/// as Moonfire NVR itself, return with all privileges.
/// 2. if `allow_unauthenticated_permissions` is configured, returns okay
/// with those permissions.
/// 2. if the caller specifies `unauth_path`, returns okay with no
/// 3. if the caller specifies `unauth_path`, returns okay with no
/// permissions.
/// 3. returns `Unauthenticated` error otherwise.
/// 4. returns `Unauthenticated` error otherwise.
///
/// Does no authorization. That is, this doesn't check that the returned
/// permissions are sufficient for whatever operation the caller is
@ -567,6 +577,7 @@ impl Service {
fn authenticate(
&self,
req: &Request<hyper::Body>,
conn_data: &ConnData,
unauth_path: bool,
) -> Result<Caller, base::Error> {
if let Some(sid) = extract_sid(req) {
@ -594,6 +605,18 @@ impl Service {
};
}
if matches!(conn_data.client_unix_uid, Some(uid) if Some(uid) == self.privileged_unix_uid) {
return Ok(Caller {
permissions: db::Permissions {
view_video: true,
read_camera_configs: true,
update_signals: true,
..Default::default()
},
user: None,
});
}
if let Some(s) = self.allow_unauthenticated_permissions.as_ref() {
return Ok(Caller {
permissions: s.clone(),
@ -638,13 +661,22 @@ mod tests {
allow_unauthenticated_permissions,
trust_forward_hdrs: true,
time_zone_name: "".to_owned(),
privileged_unix_uid: nix::unistd::Uid::from_raw(!0),
})
.unwrap(),
);
let make_svc = hyper::service::make_service_fn(move |_conn| {
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
let s = Arc::clone(&service);
move |req| Arc::clone(&s).serve(req)
move |req| {
Arc::clone(&s).serve(
req,
super::accept::ConnData {
client_unix_uid: None,
client_addr: None,
},
)
}
}))
});
let (tx, rx) = std::sync::mpsc::channel();
@ -740,13 +772,22 @@ mod bench {
allow_unauthenticated_permissions: Some(db::Permissions::default()),
trust_forward_hdrs: false,
time_zone_name: "".to_owned(),
privileged_unix_uid: nix::unistd::Uid::from_raw(!0),
})
.unwrap(),
);
let make_svc = hyper::service::make_service_fn(move |_conn| {
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
let s = Arc::clone(&service);
move |req| Arc::clone(&s).serve(req)
move |req| {
Arc::clone(&s).serve(
req,
super::accept::ConnData {
client_unix_uid: None,
client_addr: None,
},
)
}
}))
});
let rt = tokio::runtime::Runtime::new().unwrap();