From 343de637c4246b91672e11021eaf5dde69a34cf0 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Wed, 18 Nov 2020 09:21:54 -0500 Subject: [PATCH 1/9] Added ability to load from custom key and certificate paths --- src/lib.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8717bc0..2e434c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,12 @@ #[macro_use] extern crate log; -use std::{panic::AssertUnwindSafe, convert::TryFrom, io::BufReader, sync::Arc}; +use std::{ + panic::AssertUnwindSafe, + convert::TryFrom, + io::BufReader, + sync::Arc, + path::PathBuf, +}; use futures::{future::BoxFuture, FutureExt}; use tokio::{ prelude::*, @@ -95,18 +101,64 @@ impl Server { pub struct Builder { addr: A, + cert_path: PathBuf, + key_path: PathBuf, } impl Builder { fn bind(addr: A) -> Self { - Self { addr } + Self { + addr, + cert_path: PathBuf::from("cert/cert.pem"), + key_path: PathBuf::from("cert/key.pem"), + } + } + + /// Sets the directory that northstar should look for TLS certs and keys into + /// + /// Northstar will look for files called `cert.pem` and `key.pem` in the provided + /// directory. + /// + /// This does not need to be set if both [`set_cert()`](Self::set_cert()) and + /// [`set_key()`](Self::set_key()) have been called. + /// + /// If not set, the default is `cert/` + pub fn set_tls_dir(self, dir: impl Into) -> Self { + let dir = dir.into(); + self.set_cert(dir.join("cert.pem")) + .set_key(dir.join("key.pem")) + } + + /// Set the path to the TLS certificate northstar will use + /// + /// This defaults to `cert/cert.pem`. + /// + /// This does not need to be called it [`set_tls_dir()`](Self::set_tls_dir()) has been + /// called. + pub fn set_cert(mut self, cert_path: impl Into) -> Self { + self.cert_path = cert_path.into(); + self + } + + /// Set the path to the ertificate key northstar will use + /// + /// This defaults to `cert/key.pem`. + /// + /// This does not need to be called it [`set_tls_dir()`](Self::set_tls_dir()) has been + /// called. + /// + /// This should of course correspond to the key set in + /// [`set_cert()`](Self::set_cert()) + pub fn set_key(mut self, key_path: impl Into) -> Self { + self.key_path = key_path.into(); + self } pub async fn serve(self, handler: F) -> Result<()> where F: Fn(Request) -> HandlerResponse + Send + Sync + 'static, { - let config = tls_config() + let config = tls_config(&self.cert_path, &self.key_path) .context("Failed to create TLS config")?; let listener = TcpListener::bind(self.addr).await @@ -183,12 +235,12 @@ async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin)) Ok(()) } -fn tls_config() -> Result> { +fn tls_config(cert_path: &PathBuf, key_path: &PathBuf) -> Result> { let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new()); - let cert_chain = load_cert_chain() + let cert_chain = load_cert_chain(cert_path) .context("Failed to load TLS certificate")?; - let key = load_key() + let key = load_key(key_path) .context("Failed to load TLS key")?; config.set_single_cert(cert_chain, key) .context("Failed to use loaded TLS certificate")?; @@ -196,24 +248,22 @@ fn tls_config() -> Result> { Ok(config.into()) } -fn load_cert_chain() -> Result> { - let cert_path = "cert/cert.pem"; +fn load_cert_chain(cert_path: &PathBuf) -> Result> { let certs = std::fs::File::open(cert_path) - .with_context(|| format!("Failed to open `{}`", cert_path))?; + .with_context(|| format!("Failed to open `{:?}`", cert_path))?; let mut certs = BufReader::new(certs); let certs = rustls::internal::pemfile::certs(&mut certs) - .map_err(|_| anyhow!("failed to load certs `{}`", cert_path))?; + .map_err(|_| anyhow!("failed to load certs `{:?}`", cert_path))?; Ok(certs) } -fn load_key() -> Result { - let key_path = "cert/key.pem"; +fn load_key(key_path: &PathBuf) -> Result { let keys = std::fs::File::open(key_path) - .with_context(|| format!("Failed to open `{}`", key_path))?; + .with_context(|| format!("Failed to open `{:?}`", key_path))?; let mut keys = BufReader::new(keys); let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys) - .map_err(|_| anyhow!("failed to load key `{}`", key_path))?; + .map_err(|_| anyhow!("failed to load key `{:?}`", key_path))?; ensure!(!keys.is_empty(), "no key found"); From 753ecf708d6d943ce8da777044c37f86fb778e09 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Thu, 19 Nov 2020 02:02:11 -0500 Subject: [PATCH 2/9] Isolate directory serving methods behind feature, incl mime_guess. Remove itertools --- Cargo.toml | 7 +++++-- src/types/document.rs | 2 +- src/util.rs | 16 ++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd12927..a2de13d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,10 @@ description = "Gemini server implementation" repository = "https://github.com/panicbit/northstar" documentation = "https://docs.rs/northstar" +[features] +default = ["serve_dir"] +serve_dir = ["mime_guess"] + [dependencies] anyhow = "1.0.33" rustls = { version = "0.18.1", features = ["dangerous_configuration"] } @@ -17,11 +21,10 @@ mime = "0.3.16" uriparse = "0.6.3" percent-encoding = "2.1.0" futures = "0.3.7" -itertools = "0.9.0" log = "0.4.11" webpki = "0.21.0" lazy_static = "1.4.0" -mime_guess = "2.0.3" +mime_guess = { version = "2.0.3", optional = true } [dev-dependencies] env_logger = "0.8.1" diff --git a/src/types/document.rs b/src/types/document.rs index f6fc5aa..e322357 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -39,7 +39,6 @@ use std::convert::TryInto; use std::fmt; -use itertools::Itertools; use crate::types::URIReference; use crate::util::Cowy; @@ -550,5 +549,6 @@ fn strip_newlines(text: impl Cowy) -> String { text.as_ref() .lines() .filter(|part| !part.is_empty()) + .collect::>() .join(" ") } diff --git a/src/util.rs b/src/util.rs index 1ba11af..ac774a2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,13 +1,18 @@ -use std::path::Path; +#[cfg(feature="serve_dir")] +use std::path::{Path, PathBuf}; +#[cfg(feature="serve_dir")] use mime::Mime; +#[cfg(feature="serve_dir")] use anyhow::*; +#[cfg(feature="serve_dir")] use tokio::{ fs::{self, File}, io, }; +#[cfg(feature="serve_dir")] use crate::types::{Response, Document, document::HeadingLevel::*}; -use itertools::Itertools; +#[cfg(feature="serve_dir")] pub async fn serve_file>(path: P, mime: &Mime) -> Result { let path = path.as_ref(); @@ -22,6 +27,7 @@ pub async fn serve_file>(path: P, mime: &Mime) -> Result, P: AsRef>(dir: D, virtual_path: &[P]) -> Result { debug!("Dir: {}", dir.as_ref().display()); let dir = dir.as_ref().canonicalize() @@ -47,6 +53,7 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P serve_dir_listing(path, virtual_path).await } +#[cfg(feature="serve_dir")] async fn serve_dir_listing, B: AsRef>(path: P, virtual_path: &[B]) -> Result { let mut dir = match fs::read_dir(path).await { Ok(dir) => dir, @@ -56,10 +63,10 @@ async fn serve_dir_listing, B: AsRef>(path: P, virtual_path } }; - let breadcrumbs = virtual_path.iter().map(|segment| segment.as_ref().display()).join("/"); + let breadcrumbs: PathBuf = virtual_path.iter().collect(); let mut document = Document::new(); - document.add_heading(H1, format!("Index of /{}", breadcrumbs)); + document.add_heading(H1, format!("Index of /{}", breadcrumbs.display())); document.add_blank_line(); if virtual_path.get(0).map(<_>::as_ref) != Some(Path::new("")) { @@ -85,6 +92,7 @@ async fn serve_dir_listing, B: AsRef>(path: P, virtual_path Ok(Response::document(document)) } +#[cfg(feature="serve_dir")] pub fn guess_mime_from_path>(path: P) -> Mime { let path = path.as_ref(); let extension = path.extension().and_then(|s| s.to_str()); From e0abe6344bc9ac93b03898ee8c8f0453e6017c76 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Thu, 19 Nov 2020 02:54:45 -0500 Subject: [PATCH 3/9] Reduced dependency on futures-rs --- Cargo.toml | 3 ++- examples/certificates.rs | 3 ++- examples/document.rs | 3 ++- examples/serve_dir.rs | 3 ++- src/lib.rs | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2de13d..1ef35dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,8 @@ tokio = { version = "0.3.1", features = ["full"] } mime = "0.3.16" uriparse = "0.6.3" percent-encoding = "2.1.0" -futures = "0.3.7" +futures-core = "0.3.7" +futures-util = "0.3.7" log = "0.4.11" webpki = "0.21.0" lazy_static = "1.4.0" diff --git a/examples/certificates.rs b/examples/certificates.rs index f0f98b3..541fbe5 100644 --- a/examples/certificates.rs +++ b/examples/certificates.rs @@ -1,5 +1,6 @@ use anyhow::*; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::FutureExt; use log::LevelFilter; use tokio::sync::RwLock; use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server}; diff --git a/examples/document.rs b/examples/document.rs index 5986896..8ff6bbb 100644 --- a/examples/document.rs +++ b/examples/document.rs @@ -1,5 +1,6 @@ use anyhow::*; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::FutureExt; use log::LevelFilter; use northstar::{Server, Request, Response, GEMINI_PORT, Document}; use northstar::document::HeadingLevel::*; diff --git a/examples/serve_dir.rs b/examples/serve_dir.rs index 47145e1..fd26ac4 100644 --- a/examples/serve_dir.rs +++ b/examples/serve_dir.rs @@ -1,5 +1,6 @@ use anyhow::*; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::FutureExt; use log::LevelFilter; use northstar::{Server, Request, Response, GEMINI_PORT}; diff --git a/src/lib.rs b/src/lib.rs index 066ce28..2083e0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ use std::{ sync::Arc, time::Duration, }; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::future::FutureExt; use tokio::{ prelude::*, io::{self, BufStream}, From 3da18ca5306241c54a269b2806d3735d810b992a Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Thu, 19 Nov 2020 10:21:32 -0500 Subject: [PATCH 4/9] Reduced tokio featureset --- Cargo.toml | 4 ++-- src/types/body.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ef35dc..ae965a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,13 @@ documentation = "https://docs.rs/northstar" [features] default = ["serve_dir"] -serve_dir = ["mime_guess"] +serve_dir = ["mime_guess", "tokio/fs"] [dependencies] anyhow = "1.0.33" rustls = { version = "0.18.1", features = ["dangerous_configuration"] } tokio-rustls = "0.20.0" -tokio = { version = "0.3.1", features = ["full"] } +tokio = { version = "0.3.1", features = ["io-util","net","time", "rt"] } mime = "0.3.16" uriparse = "0.6.3" percent-encoding = "2.1.0" diff --git a/src/types/body.rs b/src/types/body.rs index dfeb8ca..d1356cc 100644 --- a/src/types/body.rs +++ b/src/types/body.rs @@ -1,4 +1,6 @@ -use tokio::{io::AsyncRead, fs::File}; +use tokio::io::AsyncRead; +#[cfg(feature="serve_dir")] +use tokio::fs::File; use crate::types::Document; @@ -37,6 +39,7 @@ impl<'a> From<&'a str> for Body { } } +#[cfg(feature="serve_dir")] impl From for Body { fn from(file: File) -> Self { Self::Reader(Box::new(file)) From 87d71cb207d8bd669b5125c5b58c9de57076b797 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Thu, 19 Nov 2020 11:08:20 -0500 Subject: [PATCH 5/9] Fixed examples --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ae965a5..62743e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,5 @@ mime_guess = { version = "2.0.3", optional = true } [dev-dependencies] env_logger = "0.8.1" +futures-util = "0.3.7" +tokio = { version = "0.3.1", features = ["macros", "rt-multi-thread", "sync"] } From 475db6af797fa80996fe2a8f50ce006c95c3b2a2 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Thu, 19 Nov 2020 11:09:53 -0500 Subject: [PATCH 6/9] Adapted a type from futures-util allowing us to drop the dep and remove a big chunk of the dep tree --- Cargo.toml | 1 - src/lib.rs | 5 ++--- src/util.rs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 62743e8..9ad991d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ mime = "0.3.16" uriparse = "0.6.3" percent-encoding = "2.1.0" futures-core = "0.3.7" -futures-util = "0.3.7" log = "0.4.11" webpki = "0.21.0" lazy_static = "1.4.0" diff --git a/src/lib.rs b/src/lib.rs index 2083e0f..9b1ad5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ use std::{ time::Duration, }; use futures_core::future::BoxFuture; -use futures_util::future::FutureExt; use tokio::{ prelude::*, io::{self, BufStream}, @@ -33,7 +32,7 @@ pub const REQUEST_URI_MAX_LEN: usize = 1024; pub const GEMINI_PORT: u16 = 1965; type Handler = Arc HandlerResponse + Send + Sync>; -type HandlerResponse = BoxFuture<'static, Result>; +pub (crate) type HandlerResponse = BoxFuture<'static, Result>; #[derive(Clone)] pub struct Server { @@ -94,7 +93,7 @@ impl Server { let handler = (self.handler)(request); let handler = AssertUnwindSafe(handler); - let response = handler.catch_unwind().await + let response = util::HandlerCatchUnwind::new(handler).await .unwrap_or_else(|_| Response::server_error("")) .or_else(|err| { error!("Handler failed: {:?}", err); diff --git a/src/util.rs b/src/util.rs index ac774a2..628b018 100644 --- a/src/util.rs +++ b/src/util.rs @@ -11,6 +11,9 @@ use tokio::{ }; #[cfg(feature="serve_dir")] use crate::types::{Response, Document, document::HeadingLevel::*}; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::task::Poll; +use futures_core::future::Future; #[cfg(feature="serve_dir")] pub async fn serve_file>(path: P, mime: &Mime) -> Result { @@ -123,3 +126,32 @@ where C: AsRef + Into, T: ToOwned + ?Sized, {} + +/// A utility for catching unwinds on Futures. +/// +/// This is adapted from the futures-rs CatchUnwind, in an effort to reduce the large +/// amount of dependencies tied into the feature that provides this simple struct. +#[must_use = "futures do nothing unless polled"] +pub (crate) struct HandlerCatchUnwind { + future: AssertUnwindSafe, +} + +impl HandlerCatchUnwind { + pub(super) fn new(future: AssertUnwindSafe) -> Self { + Self { future } + } +} + +impl Future for HandlerCatchUnwind { + type Output = Result, Box>; + + fn poll( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context + ) -> Poll { + match catch_unwind(AssertUnwindSafe(|| self.future.as_mut().poll(cx))) { + Ok(res) => res.map(Ok), + Err(e) => Poll::Ready(Err(e)) + } + } +} From c69cf49d995a2c341fe0e68e0fc8cd83f14640d0 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Thu, 19 Nov 2020 11:29:11 -0500 Subject: [PATCH 7/9] Removed over-zeleous feature gating --- src/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.rs b/src/util.rs index 628b018..c48b8bf 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,7 +2,6 @@ use std::path::{Path, PathBuf}; #[cfg(feature="serve_dir")] use mime::Mime; -#[cfg(feature="serve_dir")] use anyhow::*; #[cfg(feature="serve_dir")] use tokio::{ @@ -10,7 +9,8 @@ use tokio::{ io, }; #[cfg(feature="serve_dir")] -use crate::types::{Response, Document, document::HeadingLevel::*}; +use crate::types::{Document, document::HeadingLevel::*}; +use crate::types::Response; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::task::Poll; use futures_core::future::Future; From a3882b76de5f1ad7d9a2cad5805de607e6713899 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 19 Nov 2020 18:18:29 +0100 Subject: [PATCH 8/9] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa5c95c..63c75f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `redirect_temporary_lossy` for `Response` and `ResponseHeader` - `bad_request_lossy` for `Response` and `ResponseHeader` - support for a lot more mime-types in `guess_mime_from_path`, backed by the `mime_guess` crate +- customizable TLS cert & key paths by [@Alch-Emi](https://github.com/Alch-Emi) ## [0.3.0] - 2020-11-14 ### Added From 381bccf36f85ce5eeba83a205ce68bef44e3c185 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 19 Nov 2020 18:35:06 +0100 Subject: [PATCH 9/9] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c75f1..f1d7f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `bad_request_lossy` for `Response` and `ResponseHeader` - support for a lot more mime-types in `guess_mime_from_path`, backed by the `mime_guess` crate - customizable TLS cert & key paths by [@Alch-Emi](https://github.com/Alch-Emi) +- `server_dir` default feature for serve_dir utils [@Alch-Emi](https://github.com/Alch-Emi) +### Improved +- build time and size by [@Alch-Emi](https://github.com/Alch-Emi) ## [0.3.0] - 2020-11-14 ### Added