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:
Scott Lamb 2018-11-28 14:22:30 -08:00
parent 4daf618c29
commit 7a81d36562
2 changed files with 67 additions and 17 deletions

View File

@ -68,6 +68,11 @@ Options:
--read-only Forces read-only mode / disables recording.
--require-auth=BOOL Requires authentication to access the web interface.
[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)]
@ -77,6 +82,7 @@ struct Args {
flag_ui_dir: String,
flag_read_only: bool,
flag_require_auth: bool,
flag_trust_forward_hdrs: bool,
}
fn setup_shutdown() -> impl Future<Item = (), Error = ()> + Send {
@ -177,9 +183,15 @@ pub fn run() -> Result<(), Error> {
}
info!("Directories are opened.");
let zone = resolve_zone()?;
info!("Resolved timezone: {}", &zone);
let s = web::Service::new(db.clone(), Some(&args.flag_ui_dir), args.flag_require_auth, zone)?;
let time_zone_name = resolve_zone()?;
info!("Resolved timezone: {}", &time_zone_name);
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.
let shutdown_streamers = Arc::new(AtomicBool::new(false));

View File

@ -53,6 +53,7 @@ use serde_json;
use std::collections::HashMap;
use std::cmp;
use std::fs;
use std::net::IpAddr;
use std::ops::Range;
use std::path::PathBuf;
use std::sync::Arc;
@ -223,6 +224,7 @@ struct ServiceInner {
pool: futures_cpupool::CpuPool,
time_zone_name: String,
require_auth: bool,
trust_forward_hdrs: bool,
}
type ResponseResult = Result<Response<Body>, Response<Body>>;
@ -471,11 +473,22 @@ impl ServiceInner {
fn authreq(&self, req: &Request<::hyper::Body>) -> auth::Request {
auth::Request {
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()),
}
}
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 {
let mut username = None;
let mut password = None;
@ -498,11 +511,18 @@ impl ServiceInner {
None => host,
}.to_owned();
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,
flags)
.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];
base64::encode_config_slice(&sid, base64::STANDARD_NO_PAD, &mut encoded);
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
}
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)]
pub struct Service(Arc<ServiceInner>);
impl Service {
pub fn new(db: Arc<db::Database>, ui_dir: Option<&str>, require_auth: bool,
time_zone_name: String) -> Result<Self, Error> {
pub fn new(config: Config) -> Result<Self, Error> {
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);
}
debug!("UI files: {:#?}", ui_files);
let dirs_by_stream_id = {
let l = db.lock();
let l = config.db.lock();
let mut d =
FnvHashMap::with_capacity_and_hasher(l.streams_by_id().len(), Default::default());
for (&id, s) in l.streams_by_id().iter() {
@ -641,12 +668,13 @@ impl Service {
};
Ok(Service(Arc::new(ServiceInner {
db,
db: config.db,
dirs_by_stream_id,
ui_files,
pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(),
require_auth,
time_zone_name,
require_auth: config.require_auth,
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 addr = "127.0.0.1:0".parse().unwrap();
let require_auth = true;
let service = super::Service::new(db.db.clone(), None, require_auth,
"".to_owned()).unwrap();
let service = super::Service::new(super::Config {
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)
.tcp_nodelay(true)
.serve(move || Ok::<_, Box<StdError + Send + Sync>>(service.clone()));
@ -1017,8 +1050,13 @@ mod bench {
testutil::add_dummy_recordings_to_db(&db.db, 1440);
let addr = "127.0.0.1:0".parse().unwrap();
let require_auth = false;
let service = super::Service::new(db.db.clone(), None, require_auth,
"".to_owned()).unwrap();
let service = super::Service::new(super::Config {
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)
.tcp_nodelay(true)
.serve(move || Ok::<_, Box<StdError + Send + Sync>>(service.clone()));