mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-07-29 10:11:00 -04:00
support proxy forwarded headers
I went with legacy headers (X-Real-IP, X-Forwarded-Proto) because they appear to be more widely supported than the RFC 7239 Forwarded header.
This commit is contained in:
parent
4daf618c29
commit
7a81d36562
@ -68,6 +68,11 @@ Options:
|
|||||||
--read-only Forces read-only mode / disables recording.
|
--read-only Forces read-only mode / disables recording.
|
||||||
--require-auth=BOOL Requires authentication to access the web interface.
|
--require-auth=BOOL Requires authentication to access the web interface.
|
||||||
[default: true]
|
[default: true]
|
||||||
|
--trust-forward-hdrs Trust X-Real-IP: and X-Forwarded-Proto: headers on
|
||||||
|
the incoming request. Set this only after ensuring
|
||||||
|
your proxy server is configured to set them and that
|
||||||
|
no untrusted requests bypass the proxy server.
|
||||||
|
You may want to specify --http-addr=127.0.0.1:8080.
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -77,6 +82,7 @@ struct Args {
|
|||||||
flag_ui_dir: String,
|
flag_ui_dir: String,
|
||||||
flag_read_only: bool,
|
flag_read_only: bool,
|
||||||
flag_require_auth: bool,
|
flag_require_auth: bool,
|
||||||
|
flag_trust_forward_hdrs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_shutdown() -> impl Future<Item = (), Error = ()> + Send {
|
fn setup_shutdown() -> impl Future<Item = (), Error = ()> + Send {
|
||||||
@ -177,9 +183,15 @@ pub fn run() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
info!("Directories are opened.");
|
info!("Directories are opened.");
|
||||||
|
|
||||||
let zone = resolve_zone()?;
|
let time_zone_name = resolve_zone()?;
|
||||||
info!("Resolved timezone: {}", &zone);
|
info!("Resolved timezone: {}", &time_zone_name);
|
||||||
let s = web::Service::new(db.clone(), Some(&args.flag_ui_dir), args.flag_require_auth, zone)?;
|
let s = web::Service::new(web::Config {
|
||||||
|
db: db.clone(),
|
||||||
|
ui_dir: Some(&args.flag_ui_dir),
|
||||||
|
require_auth: args.flag_require_auth,
|
||||||
|
trust_forward_hdrs: args.flag_trust_forward_hdrs,
|
||||||
|
time_zone_name,
|
||||||
|
})?;
|
||||||
|
|
||||||
// Start a streamer for each stream.
|
// Start a streamer for each stream.
|
||||||
let shutdown_streamers = Arc::new(AtomicBool::new(false));
|
let shutdown_streamers = Arc::new(AtomicBool::new(false));
|
||||||
|
66
src/web.rs
66
src/web.rs
@ -53,6 +53,7 @@ use serde_json;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -223,6 +224,7 @@ struct ServiceInner {
|
|||||||
pool: futures_cpupool::CpuPool,
|
pool: futures_cpupool::CpuPool,
|
||||||
time_zone_name: String,
|
time_zone_name: String,
|
||||||
require_auth: bool,
|
require_auth: bool,
|
||||||
|
trust_forward_hdrs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseResult = Result<Response<Body>, Response<Body>>;
|
type ResponseResult = Result<Response<Body>, Response<Body>>;
|
||||||
@ -471,11 +473,22 @@ impl ServiceInner {
|
|||||||
fn authreq(&self, req: &Request<::hyper::Body>) -> auth::Request {
|
fn authreq(&self, req: &Request<::hyper::Body>) -> auth::Request {
|
||||||
auth::Request {
|
auth::Request {
|
||||||
when_sec: Some(self.db.clocks().realtime().sec),
|
when_sec: Some(self.db.clocks().realtime().sec),
|
||||||
addr: None, // TODO: req.remote_addr().map(|a| a.ip()),
|
addr: if self.trust_forward_hdrs {
|
||||||
|
req.headers().get("X-Real-IP")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|v| IpAddr::from_str(v).ok())
|
||||||
|
} else { None },
|
||||||
user_agent: req.headers().get(header::USER_AGENT).map(|ua| ua.as_bytes().to_vec()),
|
user_agent: req.headers().get(header::USER_AGENT).map(|ua| ua.as_bytes().to_vec()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_secure(&self, req: &Request<::hyper::Body>) -> bool {
|
||||||
|
self.trust_forward_hdrs &&
|
||||||
|
req.headers().get("X-Forwarded-Proto")
|
||||||
|
.map(|v| v.as_bytes() == b"https")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn login(&self, req: &Request<::hyper::Body>, body: hyper::Chunk) -> ResponseResult {
|
fn login(&self, req: &Request<::hyper::Body>, body: hyper::Chunk) -> ResponseResult {
|
||||||
let mut username = None;
|
let mut username = None;
|
||||||
let mut password = None;
|
let mut password = None;
|
||||||
@ -498,11 +511,18 @@ impl ServiceInner {
|
|||||||
None => host,
|
None => host,
|
||||||
}.to_owned();
|
}.to_owned();
|
||||||
let mut l = self.db.lock();
|
let mut l = self.db.lock();
|
||||||
let flags = (auth::SessionFlags::HttpOnly as i32) | (auth::SessionFlags::SameSite as i32);
|
let is_secure = self.is_secure(req);
|
||||||
|
let flags = (auth::SessionFlags::HttpOnly as i32) |
|
||||||
|
(auth::SessionFlags::SameSite as i32) |
|
||||||
|
if is_secure { (auth::SessionFlags::Secure as i32) } else { 0 };
|
||||||
let (sid, _) = l.login_by_password(authreq, &username, password.into_owned(), domain,
|
let (sid, _) = l.login_by_password(authreq, &username, password.into_owned(), domain,
|
||||||
flags)
|
flags)
|
||||||
.map_err(|e| plain_response(StatusCode::UNAUTHORIZED, e.to_string()))?;
|
.map_err(|e| plain_response(StatusCode::UNAUTHORIZED, e.to_string()))?;
|
||||||
let s_suffix = "; HttpOnly; SameSite=Lax; Max-Age=2147483648; Path=/";
|
let s_suffix = if is_secure {
|
||||||
|
"; HttpOnly; Secure; SameSite=Lax; Max-Age=2147483648; Path=/"
|
||||||
|
} else {
|
||||||
|
"; HttpOnly; SameSite=Lax; Max-Age=2147483648; Path=/"
|
||||||
|
};
|
||||||
let mut encoded = [0u8; 64];
|
let mut encoded = [0u8; 64];
|
||||||
base64::encode_config_slice(&sid, base64::STANDARD_NO_PAD, &mut encoded);
|
base64::encode_config_slice(&sid, base64::STANDARD_NO_PAD, &mut encoded);
|
||||||
let mut cookie = BytesMut::with_capacity("s=".len() + encoded.len() + s_suffix.len());
|
let mut cookie = BytesMut::with_capacity("s=".len() + encoded.len() + s_suffix.len());
|
||||||
@ -612,19 +632,26 @@ fn extract_sid(req: &Request<hyper::Body>) -> Option<auth::RawSessionId> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Config<'a> {
|
||||||
|
pub db: Arc<db::Database>,
|
||||||
|
pub ui_dir: Option<&'a str>,
|
||||||
|
pub require_auth: bool,
|
||||||
|
pub trust_forward_hdrs: bool,
|
||||||
|
pub time_zone_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Service(Arc<ServiceInner>);
|
pub struct Service(Arc<ServiceInner>);
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub fn new(db: Arc<db::Database>, ui_dir: Option<&str>, require_auth: bool,
|
pub fn new(config: Config) -> Result<Self, Error> {
|
||||||
time_zone_name: String) -> Result<Self, Error> {
|
|
||||||
let mut ui_files = HashMap::new();
|
let mut ui_files = HashMap::new();
|
||||||
if let Some(d) = ui_dir {
|
if let Some(d) = config.ui_dir {
|
||||||
Service::fill_ui_files(d, &mut ui_files);
|
Service::fill_ui_files(d, &mut ui_files);
|
||||||
}
|
}
|
||||||
debug!("UI files: {:#?}", ui_files);
|
debug!("UI files: {:#?}", ui_files);
|
||||||
let dirs_by_stream_id = {
|
let dirs_by_stream_id = {
|
||||||
let l = db.lock();
|
let l = config.db.lock();
|
||||||
let mut d =
|
let mut d =
|
||||||
FnvHashMap::with_capacity_and_hasher(l.streams_by_id().len(), Default::default());
|
FnvHashMap::with_capacity_and_hasher(l.streams_by_id().len(), Default::default());
|
||||||
for (&id, s) in l.streams_by_id().iter() {
|
for (&id, s) in l.streams_by_id().iter() {
|
||||||
@ -641,12 +668,13 @@ impl Service {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Service(Arc::new(ServiceInner {
|
Ok(Service(Arc::new(ServiceInner {
|
||||||
db,
|
db: config.db,
|
||||||
dirs_by_stream_id,
|
dirs_by_stream_id,
|
||||||
ui_files,
|
ui_files,
|
||||||
pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(),
|
pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(),
|
||||||
require_auth,
|
require_auth: config.require_auth,
|
||||||
time_zone_name,
|
trust_forward_hdrs: config.trust_forward_hdrs,
|
||||||
|
time_zone_name: config.time_zone_name,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,8 +834,13 @@ mod tests {
|
|||||||
let (shutdown_tx, shutdown_rx) = futures::sync::oneshot::channel::<()>();
|
let (shutdown_tx, shutdown_rx) = futures::sync::oneshot::channel::<()>();
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
let require_auth = true;
|
let require_auth = true;
|
||||||
let service = super::Service::new(db.db.clone(), None, require_auth,
|
let service = super::Service::new(super::Config {
|
||||||
"".to_owned()).unwrap();
|
db: db.db.clone(),
|
||||||
|
ui_dir: None,
|
||||||
|
require_auth,
|
||||||
|
trust_forward_hdrs: true,
|
||||||
|
time_zone_name: "".to_owned(),
|
||||||
|
}).unwrap();
|
||||||
let server = hyper::server::Server::bind(&addr)
|
let server = hyper::server::Server::bind(&addr)
|
||||||
.tcp_nodelay(true)
|
.tcp_nodelay(true)
|
||||||
.serve(move || Ok::<_, Box<StdError + Send + Sync>>(service.clone()));
|
.serve(move || Ok::<_, Box<StdError + Send + Sync>>(service.clone()));
|
||||||
@ -1017,8 +1050,13 @@ mod bench {
|
|||||||
testutil::add_dummy_recordings_to_db(&db.db, 1440);
|
testutil::add_dummy_recordings_to_db(&db.db, 1440);
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
let require_auth = false;
|
let require_auth = false;
|
||||||
let service = super::Service::new(db.db.clone(), None, require_auth,
|
let service = super::Service::new(super::Config {
|
||||||
"".to_owned()).unwrap();
|
db: db.db.clone(),
|
||||||
|
ui_dir: None,
|
||||||
|
require_auth,
|
||||||
|
trust_forward_hdrs: false,
|
||||||
|
time_zone_name: "".to_owned(),
|
||||||
|
}).unwrap();
|
||||||
let server = hyper::server::Server::bind(&addr)
|
let server = hyper::server::Server::bind(&addr)
|
||||||
.tcp_nodelay(true)
|
.tcp_nodelay(true)
|
||||||
.serve(move || Ok::<_, Box<StdError + Send + Sync>>(service.clone()));
|
.serve(move || Ok::<_, Box<StdError + Send + Sync>>(service.clone()));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user