]> OzVa Git service - ozva-cloud/commitdiff
feat: support tls
authorsigoden <sigoden@gmail.com>
Thu, 2 Jun 2022 03:06:41 +0000 (11:06 +0800)
committersigoden <sigoden@gmail.com>
Thu, 2 Jun 2022 04:15:06 +0000 (12:15 +0800)
Cargo.lock
Cargo.toml
README.md
src/args.rs
src/main.rs
src/server.rs

index a53dd1d3cd1dc203a7325f244282cbf3af5aa83c..61d7070a017ba0f118dcf28bc0b3b8a57d301ab7 100644 (file)
@@ -101,17 +101,6 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
 
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -153,6 +142,12 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "bumpalo"
+version = "3.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
+
 [[package]]
 name = "bytes"
 version = "1.1.0"
@@ -216,7 +211,7 @@ dependencies = [
  "libc",
  "num-integer",
  "num-traits",
- "time 0.1.44",
+ "time",
  "winapi 0.3.9",
 ]
 
@@ -242,17 +237,6 @@ dependencies = [
  "os_str_bytes",
 ]
 
-[[package]]
-name = "colored"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
-dependencies = [
- "atty",
- "lazy_static",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "concurrent-queue"
 version = "1.2.2"
@@ -307,18 +291,21 @@ dependencies = [
  "async-walkdir",
  "async_zip",
  "base64",
+ "chrono",
  "clap",
  "futures",
  "get_if_addrs",
  "headers",
  "hyper",
- "log",
  "mime_guess",
  "percent-encoding",
+ "rustls",
+ "rustls-pemfile",
  "serde",
  "serde_json",
- "simple_logger",
  "tokio",
+ "tokio-rustls",
+ "tokio-stream",
  "tokio-util",
 ]
 
@@ -628,6 +615,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "js-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
+dependencies = [
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -732,15 +728,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "num_threads"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "once_cell"
 version = "1.12.0"
@@ -801,12 +788,58 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
+dependencies = [
+ "base64",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
 
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "serde"
 version = "1.0.137"
@@ -849,19 +882,6 @@ dependencies = [
  "digest",
 ]
 
-[[package]]
-name = "simple_logger"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c75a9723083573ace81ad0cdfc50b858aa3c366c48636edb4109d73122a0c0ea"
-dependencies = [
- "atty",
- "colored",
- "log",
- "time 0.3.9",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "slab"
 version = "0.4.6"
@@ -878,6 +898,12 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
 [[package]]
 name = "syn"
 version = "1.0.95"
@@ -926,24 +952,6 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
-[[package]]
-name = "time"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
-dependencies = [
- "itoa",
- "libc",
- "num_threads",
- "time-macros",
-]
-
-[[package]]
-name = "time-macros"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
-
 [[package]]
 name = "tokio"
 version = "1.18.2"
@@ -973,6 +981,28 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-util"
 version = "0.7.2"
@@ -1052,6 +1082,12 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
 
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
 [[package]]
 name = "version_check"
 version = "0.9.4"
@@ -1086,6 +1122,80 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
+
+[[package]]
+name = "web-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "winapi"
 version = "0.2.8"
index 1cb8474c75c93d0bb92ad497ea28cc99eafb0486..10d860844637235007db26f89532ed64860d0dab 100644 (file)
@@ -13,21 +13,24 @@ keywords = ["static", "file", "server", "http", "cli"]
 
 [dependencies]
 clap = { version = "3", default-features = false, features = ["std", "cargo"] }
+chrono = "0.4"
 tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util"]}
+tokio-rustls = "0.23"
+tokio-stream = { version = "0.1", features = ["net"] }
+tokio-util = { version = "0.7",  features = ["codec", "io-util"] }
 hyper = { version = "0.14", features = ["http1", "server", "tcp", "stream"] }
 percent-encoding = "2.1"
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
-tokio-util = { version = "0.7", features = ["codec", "io-util"] }
 futures = "0.3"
 base64 = "0.13"
-log = "0.4"
-simple_logger = "2.1.0"
 async_zip = "0.0.7"
 async-walkdir = "0.2.0"
 headers = "0.3.7"
 mime_guess = "2.0.4"
 get_if_addrs = "0.5.3"
+rustls = { version = "0.20", default-features = false, features = ["tls12"] }
+rustls-pemfile = "1"
 
 [profile.release]
 lto = true
index f592004976b3b1922caebdd322b9c820b2c7bd00..beac6eabbfc07ecfd48edce3319f88dfd388dca9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Duf is a fully functional file server.
 - Delete files
 - Basic authentication
 - Upload zip file then unzip
+- Serve through https
 - Easy to use with curl
 
 ## Install
@@ -58,7 +59,6 @@ duf --allow-all
 duf -A
 ```
 
-
 Only allow upload operation
 
 ```
@@ -71,6 +71,12 @@ Serve a single page application (SPA)
 duf --render-spa
 ```
 
+Serve https 
+
+```
+duf --tls-cert my.crt --tls-key my.key
+```
+
 ### Api
 
 Download a file
index eb1b3e00b3fba09feade501a321fa269d4b75707..a574ab060f2ddbc202850252aadffa9c468ec6f1 100644 (file)
@@ -1,8 +1,9 @@
 use clap::crate_description;
 use clap::{Arg, ArgMatches};
-use std::env;
+use rustls::{Certificate, PrivateKey};
 use std::net::SocketAddr;
 use std::path::{Path, PathBuf};
+use std::{env, fs, io};
 
 use crate::BoxResult;
 
@@ -87,6 +88,18 @@ fn app() -> clap::Command<'static> {
                 .long("cors")
                 .help("Enable CORS, sets `Access-Control-Allow-Origin: *`"),
         )
+        .arg(
+            Arg::new("tls-cert")
+                .long("tls-cert")
+                .value_name("path")
+                .help("Path to an SSL/TLS certificate to serve with HTTPS"),
+        )
+        .arg(
+            Arg::new("tls-key")
+                .long("tls-key")
+                .value_name("path")
+                .help("Path to the SSL/TLS certificate's private key"),
+        )
 }
 
 pub fn matches() -> ArgMatches {
@@ -107,6 +120,7 @@ pub struct Args {
     pub render_index: bool,
     pub render_spa: bool,
     pub cors: bool,
+    pub tls: Option<(Vec<Certificate>, PrivateKey)>,
 }
 
 impl Args {
@@ -127,6 +141,14 @@ impl Args {
         let allow_symlink = matches.is_present("allow-all") || matches.is_present("allow-symlink");
         let render_index = matches.is_present("render-index");
         let render_spa = matches.is_present("render-spa");
+        let tls = match (matches.value_of("tls-cert"), matches.value_of("tls-key")) {
+            (Some(certs_file), Some(key_file)) => {
+                let certs = load_certs(certs_file)?;
+                let key = load_private_key(key_file)?;
+                Some((certs, key))
+            }
+            _ => None,
+        };
 
         Ok(Args {
             address,
@@ -141,6 +163,7 @@ impl Args {
             allow_symlink,
             render_index,
             render_spa,
+            tls,
         })
     }
 
@@ -179,3 +202,35 @@ impl Args {
             })
     }
 }
+
+// Load public certificate from file.
+pub fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
+    // Open certificate file.
+    let certfile =
+        fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
+    let mut reader = io::BufReader::new(certfile);
+
+    // Load and return certificate.
+    let certs = rustls_pemfile::certs(&mut reader).map_err(|_| "Failed to load certificate")?;
+    if certs.is_empty() {
+        return Err("Expected at least one certificate".into());
+    }
+    Ok(certs.into_iter().map(Certificate).collect())
+}
+
+// Load private key from file.
+pub fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
+    // Open keyfile.
+    let keyfile =
+        fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
+    let mut reader = io::BufReader::new(keyfile);
+
+    // Load and return a single private key.
+    let keys = rustls_pemfile::rsa_private_keys(&mut reader)
+        .map_err(|e| format!("There was a problem with reading private key: {:?}", e))?;
+
+    if keys.len() != 1 {
+        return Err("Expected a single private key".into());
+    }
+    Ok(PrivateKey(keys[0].to_owned()))
+}
index 6fbfb36194a1cd91df5c5b6e05ae59271229fa59..53f237f80abf4838191ea7fc46ba42292dd310b8 100644 (file)
@@ -4,16 +4,11 @@ macro_rules! bail {
     }
 }
 
-#[macro_use]
-extern crate log;
-
 mod args;
 mod server;
 
 pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
 
-use log::LevelFilter;
-
 use crate::args::{matches, Args};
 use crate::server::serve;
 
@@ -24,14 +19,6 @@ async fn main() {
 
 async fn run() -> BoxResult<()> {
     let args = Args::parse(matches())?;
-
-    if std::env::var("RUST_LOG").is_ok() {
-        simple_logger::init()?;
-    } else {
-        simple_logger::SimpleLogger::default()
-            .with_level(LevelFilter::Info)
-            .init()?;
-    }
     serve(args).await
 }
 
index 51bd95fa10030bb97a4bd6c30a308f087e241718..e84fe00180cff714979c7f4445f8ff25d4a2217c 100644 (file)
@@ -4,6 +4,7 @@ use async_walkdir::WalkDir;
 use async_zip::read::seek::ZipFileReader;
 use async_zip::write::{EntryOptions, ZipFileWriter};
 use async_zip::Compression;
+use chrono::Local;
 use futures::stream::StreamExt;
 use futures::TryStreamExt;
 use get_if_addrs::get_if_addrs;
@@ -19,6 +20,7 @@ use hyper::header::{
 use hyper::service::{make_service_fn, service_fn};
 use hyper::{Body, Method, StatusCode};
 use percent_encoding::percent_decode;
+use rustls::ServerConfig;
 use serde::Serialize;
 use std::convert::Infallible;
 use std::fs::Metadata;
@@ -28,7 +30,9 @@ use std::sync::Arc;
 use std::time::SystemTime;
 use tokio::fs::File;
 use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite};
+use tokio::net::TcpListener;
 use tokio::{fs, io};
+use tokio_rustls::TlsAcceptor;
 use tokio_util::codec::{BytesCodec, FramedRead};
 use tokio_util::io::{ReaderStream, StreamReader};
 
@@ -49,33 +53,61 @@ macro_rules! status {
 }
 
 pub async fn serve(args: Args) -> BoxResult<()> {
+    let args = Arc::new(args);
     let socket_addr = args.address()?;
-    let address = args.address.clone();
-    let port = args.port;
-    let inner = Arc::new(InnerService::new(args));
-    let make_svc = make_service_fn(move |_| {
-        let inner = inner.clone();
-        async {
-            Ok::<_, Infallible>(service_fn(move |req| {
-                let inner = inner.clone();
-                inner.call(req)
-            }))
-        }
-    });
-
-    let server = hyper::Server::try_bind(&socket_addr)?.serve(make_svc);
-    print_listening(&address, port);
-    server.await?;
+    let inner = Arc::new(InnerService::new(args.clone()));
+    if let Some((certs, key)) = args.tls.as_ref() {
+        let config = ServerConfig::builder()
+            .with_safe_defaults()
+            .with_no_client_auth()
+            .with_single_cert(certs.clone(), key.clone())?;
+        let tls_acceptor = TlsAcceptor::from(Arc::new(config));
+        let arc_acceptor = Arc::new(tls_acceptor);
+        let listener = TcpListener::bind(&socket_addr).await.unwrap();
+        let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener);
+        let incoming = hyper::server::accept::from_stream(incoming.filter_map(|socket| async {
+            match socket {
+                Ok(stream) => match arc_acceptor.clone().accept(stream).await {
+                    Ok(val) => Some(Ok::<_, Infallible>(val)),
+                    Err(_) => None,
+                },
+                Err(_) => None,
+            }
+        }));
+        let server = hyper::Server::builder(incoming).serve(make_service_fn(move |_| {
+            let inner = inner.clone();
+            async move {
+                Ok::<_, Infallible>(service_fn(move |req| {
+                    let inner = inner.clone();
+                    inner.call(req)
+                }))
+            }
+        }));
+        print_listening(args.address.as_str(), args.port, true);
+        server.await?;
+    } else {
+        let server = hyper::Server::bind(&socket_addr).serve(make_service_fn(move |_| {
+            let inner = inner.clone();
+            async move {
+                Ok::<_, Infallible>(service_fn(move |req| {
+                    let inner = inner.clone();
+                    inner.call(req)
+                }))
+            }
+        }));
+        print_listening(args.address.as_str(), args.port, false);
+        server.await?;
+    }
 
     Ok(())
 }
 
 struct InnerService {
-    args: Args,
+    args: Arc<Args>,
 }
 
 impl InnerService {
-    pub fn new(args: Args) -> Self {
+    pub fn new(args: Arc<Args>) -> Self {
         Self { args }
     }
 
@@ -84,15 +116,20 @@ impl InnerService {
         let uri = req.uri().clone();
         let cors = self.args.cors;
 
+        let timestamp = Local::now().format("%d/%b/%Y %H:%M:%S");
         let mut res = match self.handle(req).await {
             Ok(res) => {
-                info!(r#""{} {}" - {}"#, method, uri, res.status());
+                println!(r#"[{}] "{} {}" - {}"#, timestamp, method, uri, res.status());
                 res
             }
             Err(err) => {
                 let mut res = Response::default();
-                status!(res, StatusCode::INTERNAL_SERVER_ERROR);
-                error!(r#""{} {}" - {} {}"#, method, uri, res.status(), err);
+                let status = StatusCode::INTERNAL_SERVER_ERROR;
+                status!(res, status);
+                eprintln!(
+                    r#"[{}] "{} {}" - {} {}"#,
+                    timestamp, method, uri, status, err
+                );
                 res
             }
         };
@@ -314,7 +351,7 @@ impl InnerService {
         let path = path.to_owned();
         tokio::spawn(async move {
             if let Err(e) = zip_dir(&mut writer, &path).await {
-                error!("Fail to zip {}, {}", path.display(), e.to_string());
+                eprintln!("Failed to zip {}, {}", path.display(), e);
             }
         });
         let stream = ReaderStream::new(reader);
@@ -678,14 +715,15 @@ fn to_content_range(range: &Range, complete_length: u64) -> Option<ContentRange>
     })
 }
 
-fn print_listening(address: &str, port: u16) {
+fn print_listening(address: &str, port: u16, tls: bool) {
     let addrs = retrive_listening_addrs(address);
+    let protocol = if tls { "https" } else { "http" };
     if addrs.len() == 1 {
-        eprintln!("Listening on http://{}:{}", addrs[0], port);
+        eprintln!("Listening on {}://{}:{}", protocol, addrs[0], port);
     } else {
         eprintln!("Listening on:");
         for addr in addrs {
-            eprintln!("  http://{}:{}", addr, port);
+            eprintln!("  {}://{}:{}", protocol, addr, port);
         }
     }
 }