mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-12 15:33:22 -05:00
support treating own effective uid as privileged
I intend this to be an easy bootstrapping mechanism for web auth.
This commit is contained in:
parent
4ce3e511b5
commit
7c453b5f9d
@ -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)]
|
||||
|
@ -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)?;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user