mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-27 14:43:19 -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.
|
/// specify a localhost bind address.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub trust_forward_hdrs: bool,
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -336,6 +336,7 @@ async fn inner(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start the web interface(s).
|
// Start the web interface(s).
|
||||||
|
let own_euid = nix::unistd::Uid::effective();
|
||||||
let web_handles: Result<Vec<_>, Error> = config
|
let web_handles: Result<Vec<_>, Error> = config
|
||||||
.binds
|
.binds
|
||||||
.iter()
|
.iter()
|
||||||
@ -349,11 +350,13 @@ async fn inner(
|
|||||||
.map(Permissions::as_proto),
|
.map(Permissions::as_proto),
|
||||||
trust_forward_hdrs: b.trust_forward_hdrs,
|
trust_forward_hdrs: b.trust_forward_hdrs,
|
||||||
time_zone_name: time_zone_name.clone(),
|
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({
|
futures::future::ok::<_, std::convert::Infallible>(service_fn({
|
||||||
let svc = Arc::clone(&svc);
|
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)?;
|
let listener = make_listener(&b.address)?;
|
||||||
|
@ -28,8 +28,10 @@ impl Accept for Listener {
|
|||||||
}
|
}
|
||||||
Some(Ok(Conn {
|
Some(Ok(Conn {
|
||||||
stream: Stream::Tcp(s),
|
stream: Stream::Tcp(s),
|
||||||
|
data: ConnData {
|
||||||
client_unix_uid: None,
|
client_unix_uid: None,
|
||||||
client_addr: Some(a),
|
client_addr: Some(a),
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
Listener::Unix(l) => Pin::new(l).poll_accept(cx)?.map(|(s, _a)| {
|
Listener::Unix(l) => Pin::new(l).poll_accept(cx)?.map(|(s, _a)| {
|
||||||
@ -39,8 +41,10 @@ impl Accept for Listener {
|
|||||||
};
|
};
|
||||||
Some(Ok(Conn {
|
Some(Ok(Conn {
|
||||||
stream: Stream::Unix(s),
|
stream: Stream::Unix(s),
|
||||||
client_unix_uid: Some(ucred.uid()),
|
data: ConnData {
|
||||||
|
client_unix_uid: Some(nix::unistd::Uid::from_raw(ucred.uid())),
|
||||||
client_addr: None,
|
client_addr: None,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -50,19 +54,19 @@ impl Accept for Listener {
|
|||||||
/// An open connection.
|
/// An open connection.
|
||||||
pub struct Conn {
|
pub struct Conn {
|
||||||
stream: Stream,
|
stream: Stream,
|
||||||
client_unix_uid: Option<libc::uid_t>,
|
data: ConnData,
|
||||||
client_addr: Option<std::net::SocketAddr>,
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
impl Conn {
|
||||||
#[allow(dead_code)] // TODO: feed this onward.
|
pub fn data(&self) -> &ConnData {
|
||||||
pub fn client_unix_uid(&self) -> Option<libc::uid_t> {
|
&self.data
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ mod signals;
|
|||||||
mod static_file;
|
mod static_file;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
use self::accept::ConnData;
|
||||||
use self::path::Path;
|
use self::path::Path;
|
||||||
use crate::body::Body;
|
use crate::body::Body;
|
||||||
use crate::json;
|
use crate::json;
|
||||||
@ -88,6 +89,7 @@ fn from_base_error(err: base::Error) -> Response<Body> {
|
|||||||
plain_response(status_code, err.to_string())
|
plain_response(status_code, err.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Caller {
|
struct Caller {
|
||||||
permissions: db::Permissions,
|
permissions: db::Permissions,
|
||||||
user: Option<json::ToplevelUser>,
|
user: Option<json::ToplevelUser>,
|
||||||
@ -160,6 +162,7 @@ pub struct Config<'a> {
|
|||||||
pub trust_forward_hdrs: bool,
|
pub trust_forward_hdrs: bool,
|
||||||
pub time_zone_name: String,
|
pub time_zone_name: String,
|
||||||
pub allow_unauthenticated_permissions: Option<db::Permissions>,
|
pub allow_unauthenticated_permissions: Option<db::Permissions>,
|
||||||
|
pub privileged_unix_uid: Option<nix::unistd::Uid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
@ -169,6 +172,7 @@ pub struct Service {
|
|||||||
time_zone_name: String,
|
time_zone_name: String,
|
||||||
allow_unauthenticated_permissions: Option<db::Permissions>,
|
allow_unauthenticated_permissions: Option<db::Permissions>,
|
||||||
trust_forward_hdrs: bool,
|
trust_forward_hdrs: bool,
|
||||||
|
privileged_unix_uid: Option<nix::unistd::Uid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful HTTP `Cache-Control` values to set on successful (HTTP 200) API responses.
|
/// 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,
|
allow_unauthenticated_permissions: config.allow_unauthenticated_permissions,
|
||||||
trust_forward_hdrs: config.trust_forward_hdrs,
|
trust_forward_hdrs: config.trust_forward_hdrs,
|
||||||
time_zone_name: config.time_zone_name,
|
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)?,
|
self.init_segment(sha1, debug, &req)?,
|
||||||
),
|
),
|
||||||
Path::TopLevel => (CacheControl::PrivateDynamic, self.top_level(&req, caller)?),
|
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::Camera(uuid) => (CacheControl::PrivateDynamic, self.camera(&req, uuid)?),
|
||||||
Path::StreamRecordings(uuid, type_) => (
|
Path::StreamRecordings(uuid, type_) => (
|
||||||
CacheControl::PrivateDynamic,
|
CacheControl::PrivateDynamic,
|
||||||
@ -298,6 +303,7 @@ impl Service {
|
|||||||
pub async fn serve(
|
pub async fn serve(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
req: Request<::hyper::Body>,
|
req: Request<::hyper::Body>,
|
||||||
|
conn_data: ConnData,
|
||||||
) -> Result<Response<Body>, std::convert::Infallible> {
|
) -> Result<Response<Body>, std::convert::Infallible> {
|
||||||
let p = Path::decode(req.uri().path());
|
let p = Path::decode(req.uri().path());
|
||||||
let always_allow_unauthenticated = matches!(
|
let always_allow_unauthenticated = matches!(
|
||||||
@ -305,7 +311,7 @@ impl Service {
|
|||||||
Path::NotFound | Path::Request | Path::Login | Path::Logout | Path::Static
|
Path::NotFound | Path::Request | Path::Login | Path::Logout | Path::Static
|
||||||
);
|
);
|
||||||
debug!("request on: {}: {:?}", req.uri(), p);
|
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,
|
Ok(c) => c,
|
||||||
Err(e) => return Ok(from_base_error(e)),
|
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 authreq = self.authreq(req);
|
||||||
let host = req
|
let host = req
|
||||||
.headers()
|
.headers()
|
||||||
@ -523,7 +529,8 @@ impl Service {
|
|||||||
host: {:?}\n\
|
host: {:?}\n\
|
||||||
addr: {:?}\n\
|
addr: {:?}\n\
|
||||||
user_agent: {:?}\n\
|
user_agent: {:?}\n\
|
||||||
secure: {:?}",
|
secure: {:?}\n\
|
||||||
|
caller: {:?}\n",
|
||||||
time::at(time::Timespec {
|
time::at(time::Timespec {
|
||||||
sec: authreq.when_sec.unwrap(),
|
sec: authreq.when_sec.unwrap(),
|
||||||
nsec: 0
|
nsec: 0
|
||||||
@ -534,7 +541,8 @@ impl Service {
|
|||||||
host.as_deref(),
|
host.as_deref(),
|
||||||
&authreq.addr,
|
&authreq.addr,
|
||||||
agent.as_deref(),
|
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.
|
/// Authenticates the session (if any) and returns a Caller.
|
||||||
///
|
///
|
||||||
/// If there's no session,
|
/// 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.
|
/// 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.
|
/// permissions.
|
||||||
/// 3. returns `Unauthenticated` error otherwise.
|
/// 4. returns `Unauthenticated` error otherwise.
|
||||||
///
|
///
|
||||||
/// Does no authorization. That is, this doesn't check that the returned
|
/// Does no authorization. That is, this doesn't check that the returned
|
||||||
/// permissions are sufficient for whatever operation the caller is
|
/// permissions are sufficient for whatever operation the caller is
|
||||||
@ -567,6 +577,7 @@ impl Service {
|
|||||||
fn authenticate(
|
fn authenticate(
|
||||||
&self,
|
&self,
|
||||||
req: &Request<hyper::Body>,
|
req: &Request<hyper::Body>,
|
||||||
|
conn_data: &ConnData,
|
||||||
unauth_path: bool,
|
unauth_path: bool,
|
||||||
) -> Result<Caller, base::Error> {
|
) -> Result<Caller, base::Error> {
|
||||||
if let Some(sid) = extract_sid(req) {
|
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() {
|
if let Some(s) = self.allow_unauthenticated_permissions.as_ref() {
|
||||||
return Ok(Caller {
|
return Ok(Caller {
|
||||||
permissions: s.clone(),
|
permissions: s.clone(),
|
||||||
@ -638,13 +661,22 @@ mod tests {
|
|||||||
allow_unauthenticated_permissions,
|
allow_unauthenticated_permissions,
|
||||||
trust_forward_hdrs: true,
|
trust_forward_hdrs: true,
|
||||||
time_zone_name: "".to_owned(),
|
time_zone_name: "".to_owned(),
|
||||||
|
privileged_unix_uid: nix::unistd::Uid::from_raw(!0),
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
let make_svc = hyper::service::make_service_fn(move |_conn| {
|
let make_svc = hyper::service::make_service_fn(move |_conn| {
|
||||||
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
|
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
|
||||||
let s = Arc::clone(&service);
|
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();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
@ -740,13 +772,22 @@ mod bench {
|
|||||||
allow_unauthenticated_permissions: Some(db::Permissions::default()),
|
allow_unauthenticated_permissions: Some(db::Permissions::default()),
|
||||||
trust_forward_hdrs: false,
|
trust_forward_hdrs: false,
|
||||||
time_zone_name: "".to_owned(),
|
time_zone_name: "".to_owned(),
|
||||||
|
privileged_unix_uid: nix::unistd::Uid::from_raw(!0),
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
let make_svc = hyper::service::make_service_fn(move |_conn| {
|
let make_svc = hyper::service::make_service_fn(move |_conn| {
|
||||||
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
|
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
|
||||||
let s = Arc::clone(&service);
|
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();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user