From 485c4d55d496a1a5c8490c6738e623581a9e849d Mon Sep 17 00:00:00 2001 From: jspspike Date: Wed, 5 Aug 2020 15:53:49 -0500 Subject: [PATCH 1/8] Moved to hyper_rustls --- Cargo.lock | 144 +++++++++++++++++++++++++++++++- Cargo.toml | 4 +- src/commands/dev/edge/server.rs | 2 +- src/commands/dev/gcs/server.rs | 2 +- src/commands/dev/mod.rs | 10 ++- src/commands/dev/tls/certs.rs | 88 +++++++++++++++++++ src/commands/dev/tls/mod.rs | 86 +++++++++++++++++++ src/main.rs | 13 ++- src/settings/toml/dev.rs | 1 + 9 files changed, 341 insertions(+), 9 deletions(-) create mode 100644 src/commands/dev/tls/certs.rs create mode 100644 src/commands/dev/tls/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e58bd429e..16ae193d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,6 +473,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ct-logs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8e13110a84b6315df212c045be706af261fd364791cad863285439ebba672e" +dependencies = [ + "sct", +] + [[package]] name = "curl" version = "0.4.31" @@ -1005,6 +1014,24 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6" +dependencies = [ + "bytes 0.5.6", + "ct-logs", + "futures-util", + "hyper", + "log 0.4.11", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "webpki", +] + [[package]] name = "hyper-tls" version = "0.4.3" @@ -1362,8 +1389,8 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", - "security-framework-sys", + "security-framework 0.4.4", + "security-framework-sys 0.4.3", "tempfile", ] @@ -1795,6 +1822,21 @@ dependencies = [ "winreg", ] +[[package]] +name = "ring" +version = "0.16.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + [[package]] name = "rle-decode-fast" version = "1.0.1" @@ -1825,6 +1867,31 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustls" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac94b333ee2aac3284c5b8a1b7fb4dd11cba88c244e3fe33cdbd047af0eb693" +dependencies = [ + "base64 0.12.3", + "log 0.4.11", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629d439a7672da82dd955498445e496ee2096fe2117b9f796558a43fdb9e59b8" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework 1.0.0", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1850,6 +1917,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "0.4.4" @@ -1860,7 +1937,20 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", - "security-framework-sys", + "security-framework-sys 0.4.3", +] + +[[package]] +name = "security-framework" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad502866817f0575705bd7be36e2b2535cc33262d493aa733a2ec862baa2bc2b" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys 1.0.0", ] [[package]] @@ -1873,6 +1963,16 @@ dependencies = [ "libc", ] +[[package]] +name = "security-framework-sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ceb04988b17b6d1dcd555390fa822ca5637b4a14e1f5099f13d351bed4d6c7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.10.0" @@ -2122,6 +2222,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 = "static_assertions" version = "1.1.0" @@ -2353,6 +2459,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228139ddd4fea3fa345a29233009635235833e52807af7ea6448ead03890d6a9" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-tls" version = "0.3.1" @@ -2538,6 +2656,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.1.1" @@ -2696,6 +2820,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "which" version = "4.0.2" @@ -2784,7 +2918,7 @@ dependencies = [ "futures-util", "http", "hyper", - "hyper-tls", + "hyper-rustls", "ignore", "indicatif", "lazy_static", @@ -2798,6 +2932,7 @@ dependencies = [ "rand", "regex", "reqwest", + "rustls", "semver", "serde 1.0.114", "serde_json", @@ -2806,6 +2941,7 @@ dependencies = [ "term_size", "text_io", "tokio", + "tokio-rustls", "tokio-tls", "tokio-tungstenite", "toml", diff --git a/Cargo.toml b/Cargo.toml index a640dbde2..3f19afd0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ fs2 = "0.4.3" futures-util = "0.3" http = "0.2.1" hyper = "0.13.7" -hyper-tls = "0.4.3" +hyper-rustls = "0.21.0" ignore = "0.4.16" indicatif = "0.15.0" lazy_static = "1.4.0" @@ -53,6 +53,7 @@ tempfile = "3.1.0" term_size = "0.3" text_io = "0.1.8" tokio = { version = "0.2", default-features = false, features = ["io-std", "time", "macros", "process", "signal", "sync"] } +tokio-rustls = "0.14.0" tokio-tls = "0.3.1" tokio-tungstenite = { version = "0.10.1", features = ["tls"] } toml = "0.5.6" @@ -61,6 +62,7 @@ url = "2.1.1" uuid = { version = "0.8", features = ["v4"] } which = "4.0.2" ws = "0.9.1" +rustls = "0.18.0" [dev-dependencies] assert_cmd = "1.0.1" diff --git a/src/commands/dev/edge/server.rs b/src/commands/dev/edge/server.rs index 1b78720fe..b520d06c3 100644 --- a/src/commands/dev/edge/server.rs +++ b/src/commands/dev/edge/server.rs @@ -9,7 +9,7 @@ use hyper::client::{HttpConnector, ResponseFuture}; use hyper::header::{HeaderName, HeaderValue}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client as HyperClient, Request, Server}; -use hyper_tls::HttpsConnector; +use hyper_rustls::HttpsConnector; pub(super) async fn serve( server_config: ServerConfig, diff --git a/src/commands/dev/gcs/server.rs b/src/commands/dev/gcs/server.rs index 06a3e0f4e..9be95f476 100644 --- a/src/commands/dev/gcs/server.rs +++ b/src/commands/dev/gcs/server.rs @@ -11,7 +11,7 @@ use hyper::header::{HeaderName, HeaderValue}; use hyper::http::uri::InvalidUri; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client as HyperClient, Request, Response, Server, Uri}; -use hyper_tls::HttpsConnector; +use hyper_rustls::HttpsConnector; const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com"; diff --git a/src/commands/dev/mod.rs b/src/commands/dev/mod.rs index 9bd91a294..13f51fa17 100644 --- a/src/commands/dev/mod.rs +++ b/src/commands/dev/mod.rs @@ -2,6 +2,7 @@ mod edge; mod gcs; mod server_config; mod socket; +mod tls; mod utils; use server_config::ServerConfig; @@ -19,6 +20,7 @@ pub fn dev( host: Option<&str>, port: Option, ip: Option<&str>, + http: bool, verbose: bool, ) -> Result<(), failure::Error> { let server_config = ServerConfig::new(host, ip, port)?; @@ -31,6 +33,12 @@ pub fn dev( Some(user) => edge::dev(target, user, server_config, deploy_config, verbose), // unauthenticated users connect to gcs - None => gcs::dev(target, server_config, verbose), + None => { + if http { + failure::bail!("Unauthenticated dev must use https") + } else { + gcs::dev(target, server_config, verbose) + } + } } } diff --git a/src/commands/dev/tls/certs.rs b/src/commands/dev/tls/certs.rs new file mode 100644 index 000000000..3ee088e82 --- /dev/null +++ b/src/commands/dev/tls/certs.rs @@ -0,0 +1,88 @@ +use openssl::asn1::Asn1Time; +use openssl::bn::{BigNum, MsbOption}; +use openssl::hash::MessageDigest; +use openssl::pkey::PKey; +use openssl::rsa::Rsa; +use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier}; +use openssl::x509::{X509NameBuilder, X509}; +use std::fs; +use std::path::PathBuf; + +use crate::settings::get_wrangler_home_dir; +use crate::terminal::message; + +/// Create files for cert and private key +fn create_output_files() -> Result, failure::Error> { + let home = get_wrangler_home_dir()?.join("config"); + let cert = home.join("dev-cert.pem"); + let privkey = home.join("dev-privkey.rsa"); + + if cert.exists() && privkey.exists() { + Ok(None) + } else { + fs::create_dir_all(&home)?; + + message::info(format!("Generating certificate and private key for https server, if you would like to use your own you can replace `dev-cert.pem` and `dev-privkey.rsa` at {}", home.to_str().unwrap()).as_str()); + + Ok(Some((cert, privkey))) + } +} + +/// Generate cert and private key for local https server +pub fn generate_cert() -> Result<(), failure::Error> { + let files = create_output_files()?; + if files.is_none() { + return Ok(()); + } + + let (cert_file, priv_file) = files.unwrap(); + + let rsa = Rsa::generate(2048)?; + let privkey = PKey::from_rsa(rsa)?; + + let mut x509_name = X509NameBuilder::new()?; + x509_name.append_entry_by_text("C", "US")?; + x509_name.append_entry_by_text("ST", "TX")?; + x509_name.append_entry_by_text("O", "Example")?; + x509_name.append_entry_by_text("CN", "example")?; + let x509_name = x509_name.build(); + + let mut cert_builder = X509::builder()?; + cert_builder.set_version(2)?; + let serial_number = { + let mut serial = BigNum::new()?; + serial.rand(159, MsbOption::MAYBE_ZERO, false)?; + serial.to_asn1_integer()? + }; + cert_builder.set_serial_number(&serial_number)?; + cert_builder.set_subject_name(&x509_name)?; + cert_builder.set_issuer_name(&x509_name)?; + cert_builder.set_pubkey(&privkey)?; + let not_before = Asn1Time::days_from_now(0)?; + cert_builder.set_not_before(¬_before)?; + let not_after = Asn1Time::days_from_now(365)?; + cert_builder.set_not_after(¬_after)?; + + cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; + cert_builder.append_extension( + KeyUsage::new() + .critical() + .key_cert_sign() + .crl_sign() + .build()?, + )?; + + let subject_key_identifier = + SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; + cert_builder.append_extension(subject_key_identifier)?; + + cert_builder.sign(&privkey, MessageDigest::sha256())?; + let cert_str = cert_builder.build().to_pem().unwrap(); + + let priv_str = privkey.private_key_to_pem_pkcs8().unwrap(); + + fs::write(cert_file, cert_str)?; + fs::write(priv_file, priv_str)?; + + Ok(()) +} diff --git a/src/commands/dev/tls/mod.rs b/src/commands/dev/tls/mod.rs new file mode 100644 index 000000000..557de589e --- /dev/null +++ b/src/commands/dev/tls/mod.rs @@ -0,0 +1,86 @@ +mod certs; +pub use certs::generate_cert; + +use core::task::{Context, Poll}; +use fs::File; +use futures_util::stream::Stream; +use rustls::internal::pemfile; +use rustls::{NoClientAuth, ServerConfig}; +use std::path::Path; +use std::pin::Pin; +use std::sync::Arc; +use std::vec::Vec; +use std::{fs, io}; +use tokio::net::TcpStream; +use tokio_rustls::{server::TlsStream, TlsAcceptor}; + +// Build TLS configuration +pub(super) fn get_tls_acceptor() -> Result { + // Load public certificate + let certs = load_certs("sample.pem")?; + + // Load private key + let key = load_private_key("sample.rsa")?; + + // Do not use client certificate authentication. + let mut cfg = ServerConfig::new(NoClientAuth::new()); + + // Select a certificate to use. + cfg.set_single_cert(certs, key) + .map_err(|e| io_error(format!("{}", e)))?; + + Ok(TlsAcceptor::from(Arc::new(cfg))) +} + +pub(super) fn io_error(err: String) -> io::Error { + io::Error::new(io::ErrorKind::Other, err) +} + +pub(super) struct HyperAcceptor<'a> { + pub(super) acceptor: + Pin, io::Error>> + Send + 'a>>, +} + +impl hyper::server::accept::Accept for HyperAcceptor<'_> { + type Conn = TlsStream; + type Error = io::Error; + + fn poll_accept( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll>> { + Pin::new(&mut self.acceptor).poll_next(cx) + } +} + +fn get_tls_file(filename: &str) -> Result { + let path = Path::new(&dirs::home_dir().unwrap()) + .join("Documents/work/wrangler/tls") + .join(&filename); + File::open(&path).map_err(|e| io_error(format!("failed to open {}: {}", filename, e))) +} + +// Load public certificate from file. +fn load_certs(filename: &str) -> io::Result> { + // Open certificate file. + let certfile = get_tls_file(&filename)?; + let mut reader = io::BufReader::new(certfile); + + // Load and return certificate. + pemfile::certs(&mut reader).map_err(|_| io_error("failed to load certificate".into())) +} + +// Load private key from file. +fn load_private_key(filename: &str) -> io::Result { + // Open keyfile. + let keyfile = get_tls_file(&filename)?; + let mut reader = io::BufReader::new(keyfile); + + // Load and return a single private key. + let keys = pemfile::rsa_private_keys(&mut reader) + .map_err(|_| io_error("failed to load private key".into()))?; + if keys.len() != 1 { + return Err(io_error("expected a single private key".into())); + } + Ok(keys[0].clone()) +} diff --git a/src/main.rs b/src/main.rs index 0415a23ec..3d637cb57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -521,6 +521,12 @@ fn run() -> Result<(), failure::Error> { .long("ip") .takes_value(true) ) + .arg( + Arg::with_name("http") + .help("Runs local server on http instead of https, only valid when running dev authenticated") + .long("http") + .takes_value(false) + ) .arg(verbose_arg.clone()) .arg(wrangler_file.clone()) ) @@ -764,6 +770,7 @@ fn run() -> Result<(), failure::Error> { let mut port: Option = matches .value_of("port") .map(|p| p.parse().expect("--port expects a number")); + let mut http: bool = matches.is_present("http"); // Check if arg not given but present in wrangler.toml if let Some(d) = &manifest.dev { @@ -774,6 +781,10 @@ fn run() -> Result<(), failure::Error> { if port.is_none() && d.port.is_some() { port = d.port; } + + if !http && d.http.is_some() { + http = d.http.unwrap(); + } } let env = matches.value_of("env"); @@ -782,7 +793,7 @@ fn run() -> Result<(), failure::Error> { let target = manifest.get_target(env, is_preview)?; let user = settings::global_user::GlobalUser::new().ok(); let verbose = matches.is_present("verbose"); - commands::dev::dev(target, deploy_config, user, host, port, ip, verbose)?; + commands::dev::dev(target, deploy_config, user, host, port, ip, http, verbose)?; } else if matches.subcommand_matches("whoami").is_some() { log::info!("Getting User settings"); let user = settings::global_user::GlobalUser::new()?; diff --git a/src/settings/toml/dev.rs b/src/settings/toml/dev.rs index d3fd9cf69..7346b1c3a 100644 --- a/src/settings/toml/dev.rs +++ b/src/settings/toml/dev.rs @@ -5,4 +5,5 @@ use serde::{Deserialize, Serialize}; pub struct Dev { pub ip: Option, pub port: Option, + pub http: Option, } From e1206c2bbf2e4655a8346533e5485dd146d15e78 Mon Sep 17 00:00:00 2001 From: jspspike Date: Thu, 6 Aug 2020 13:37:49 -0500 Subject: [PATCH 2/8] Started on https in gcs --- Cargo.toml | 2 +- src/commands/dev/gcs/mod.rs | 19 +++++++++++----- src/commands/dev/gcs/server.rs | 40 +++++++++++++++++++++++++++------- src/commands/dev/tls/mod.rs | 31 ++++++++++++++------------ 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f19afd0c..b97422f93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ flate2 = "1.0.16" fs2 = "0.4.3" futures-util = "0.3" http = "0.2.1" -hyper = "0.13.7" +hyper = { version = "0.13.7"} hyper-rustls = "0.21.0" ignore = "0.4.16" indicatif = "0.15.0" diff --git a/src/commands/dev/gcs/mod.rs b/src/commands/dev/gcs/mod.rs index 3311a7342..613d11a8d 100644 --- a/src/commands/dev/gcs/mod.rs +++ b/src/commands/dev/gcs/mod.rs @@ -73,14 +73,21 @@ pub fn dev( // and we must block the main thread on the completion of // said futures runtime.block_on(async { - let devtools_listener = tokio::spawn(socket::listen(socket_url)); - let server = tokio::spawn(serve(server_config, Arc::clone(&preview_id))); - let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); + let mut running = true; + let devtools_listener = tokio::spawn(socket::listen(socket_url.clone())); - match res { - Ok(_) => Ok(()), - Err(e) => Err(e), + while running { + let server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); + + //let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); + let res = server.await; + running = match res { + Ok(_) => false, + Err(_) => true, + } } + + devtools_listener.await? }) } diff --git a/src/commands/dev/gcs/server.rs b/src/commands/dev/gcs/server.rs index 9be95f476..771380ded 100644 --- a/src/commands/dev/gcs/server.rs +++ b/src/commands/dev/gcs/server.rs @@ -1,7 +1,8 @@ use crate::commands::dev::gcs::headers::{destructure_response, structure_request}; use crate::commands::dev::server_config::ServerConfig; use crate::commands::dev::utils::get_path_as_str; -use crate::terminal::emoji; +use crate::commands::dev::tls; +use crate::terminal::{emoji, message}; use std::sync::{Arc, Mutex}; @@ -12,6 +13,11 @@ use hyper::http::uri::InvalidUri; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client as HyperClient, Request, Response, Server, Uri}; use hyper_rustls::HttpsConnector; +use tokio::net::TcpListener; +use futures_util::{ + future::TryFutureExt, + stream::{StreamExt, TryStreamExt}, +}; const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com"; @@ -21,16 +27,34 @@ pub(super) async fn serve( server_config: ServerConfig, preview_id: Arc>, ) -> Result<(), failure::Error> { + tls::generate_cert()?; + // set up https client to connect to the preview service let https = HttpsConnector::new(); let client = HyperClient::builder().build::<_, Body>(https); let listening_address = server_config.listening_address; + // Create a TCP listener via tokio. + let mut tcp = TcpListener::bind(&listening_address).await?; + let tls_acceptor = tls::get_tls_acceptor()?; + // Prepare a long-running future stream to accept and serve cients. + + + let incoming_tls_stream = tcp + .incoming() + .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + .and_then(move |s| { + tls_acceptor + .accept(s) + .map_err(|e| tls::io_error(format!("TLS Error: {:?}", e))) + }) + .boxed(); + // create a closure that hyper will use later to handle HTTP requests // this takes care of sending an incoming request along to // the uploaded Worker script and returning its response - let make_service = make_service_fn(move |_| { + let service = make_service_fn(move |_| { let client = client.to_owned(); let server_config = server_config.to_owned(); let preview_id = preview_id.to_owned(); @@ -85,16 +109,16 @@ pub(super) async fn serve( } }); - let server = Server::bind(&listening_address).serve(make_service); + let server = Server::builder(tls::HyperAcceptor{ acceptor: incoming_tls_stream }).serve(service); println!( - "{} Listening on http://{}", + "{} Listening on https://{}", emoji::EAR, listening_address.to_string() ); - if let Err(e) = server.await { - eprintln!("server error: {}", e); - } - Ok(()) + + message::info("Generated certifiacte is not verified, browsers will give a warning and curl will require `--inscure`"); + + Ok(server.await?) } fn get_preview_url(path_string: &str) -> Result { diff --git a/src/commands/dev/tls/mod.rs b/src/commands/dev/tls/mod.rs index 557de589e..22b5f1a18 100644 --- a/src/commands/dev/tls/mod.rs +++ b/src/commands/dev/tls/mod.rs @@ -6,21 +6,27 @@ use fs::File; use futures_util::stream::Stream; use rustls::internal::pemfile; use rustls::{NoClientAuth, ServerConfig}; -use std::path::Path; use std::pin::Pin; use std::sync::Arc; use std::vec::Vec; +use std::path::PathBuf; use std::{fs, io}; use tokio::net::TcpStream; use tokio_rustls::{server::TlsStream, TlsAcceptor}; +use crate::settings::get_wrangler_home_dir; + // Build TLS configuration -pub(super) fn get_tls_acceptor() -> Result { +pub(super) fn get_tls_acceptor() -> Result { + let home = get_wrangler_home_dir()?.join("config"); + let cert = home.join("dev-cert.pem"); + let privkey = home.join("dev-privkey.rsa"); + // Load public certificate - let certs = load_certs("sample.pem")?; + let certs = load_certs(cert)?; // Load private key - let key = load_private_key("sample.rsa")?; + let key = load_private_key(privkey)?; // Do not use client certificate authentication. let mut cfg = ServerConfig::new(NoClientAuth::new()); @@ -53,17 +59,14 @@ impl hyper::server::accept::Accept for HyperAcceptor<'_> { } } -fn get_tls_file(filename: &str) -> Result { - let path = Path::new(&dirs::home_dir().unwrap()) - .join("Documents/work/wrangler/tls") - .join(&filename); - File::open(&path).map_err(|e| io_error(format!("failed to open {}: {}", filename, e))) +fn get_tls_file(file: PathBuf) -> Result { + File::open(&file) } // Load public certificate from file. -fn load_certs(filename: &str) -> io::Result> { +fn load_certs(file: PathBuf) -> io::Result> { // Open certificate file. - let certfile = get_tls_file(&filename)?; + let certfile = get_tls_file(file)?; let mut reader = io::BufReader::new(certfile); // Load and return certificate. @@ -71,13 +74,13 @@ fn load_certs(filename: &str) -> io::Result> { } // Load private key from file. -fn load_private_key(filename: &str) -> io::Result { +fn load_private_key(file: PathBuf) -> io::Result { // Open keyfile. - let keyfile = get_tls_file(&filename)?; + let keyfile = get_tls_file(file)?; let mut reader = io::BufReader::new(keyfile); // Load and return a single private key. - let keys = pemfile::rsa_private_keys(&mut reader) + let keys = pemfile::pkcs8_private_keys(&mut reader) .map_err(|_| io_error("failed to load private key".into()))?; if keys.len() != 1 { return Err(io_error("expected a single private key".into())); From 6e6df29c5f590492ba457a287cf4929f334543c6 Mon Sep 17 00:00:00 2001 From: jspspike Date: Thu, 6 Aug 2020 19:04:16 -0500 Subject: [PATCH 3/8] Added https to edge --- Cargo.toml | 2 +- src/commands/dev/edge/mod.rs | 55 ++++++++--- .../dev/edge/{server.rs => server/http.rs} | 40 ++------ src/commands/dev/edge/server/https.rs | 98 +++++++++++++++++++ src/commands/dev/edge/server/mod.rs | 46 +++++++++ src/commands/dev/gcs/mod.rs | 13 +-- src/commands/dev/gcs/server.rs | 50 +++++----- src/commands/dev/mod.rs | 10 +- src/commands/dev/tls/mod.rs | 2 +- src/main.rs | 5 +- 10 files changed, 232 insertions(+), 89 deletions(-) rename src/commands/dev/edge/{server.rs => server/http.rs} (70%) create mode 100644 src/commands/dev/edge/server/https.rs create mode 100644 src/commands/dev/edge/server/mod.rs diff --git a/Cargo.toml b/Cargo.toml index b97422f93..3f19afd0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ flate2 = "1.0.16" fs2 = "0.4.3" futures-util = "0.3" http = "0.2.1" -hyper = { version = "0.13.7"} +hyper = "0.13.7" hyper-rustls = "0.21.0" ignore = "0.4.16" indicatif = "0.15.0" diff --git a/src/commands/dev/edge/mod.rs b/src/commands/dev/edge/mod.rs index e96de2f90..74d449145 100644 --- a/src/commands/dev/edge/mod.rs +++ b/src/commands/dev/edge/mod.rs @@ -2,7 +2,6 @@ mod server; mod setup; mod watch; -use server::serve; use setup::{upload, Session}; use watch::watch_for_changes; @@ -20,6 +19,7 @@ pub fn dev( user: GlobalUser, server_config: ServerConfig, deploy_config: DeployConfig, + http: bool, verbose: bool, ) -> Result<(), failure::Error> { let session = Session::new(&target, &user, &deploy_config)?; @@ -53,17 +53,48 @@ pub fn dev( let mut runtime = TokioRuntime::new()?; runtime.block_on(async { - let devtools_listener = tokio::spawn(socket::listen(session.websocket_url)); - let server = tokio::spawn(serve( - server_config, - Arc::clone(&preview_token), - session.host, - )); - let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); - - match res { - Ok(_) => Ok(()), - Err(e) => Err(e), + let devtools_listener = tokio::spawn(socket::listen(session.clone().websocket_url)); + if http { + start_http(server_config, preview_token, session.clone()).await?; + } else { + start_https(server_config, preview_token, session.clone()).await; } + + //let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); + devtools_listener.await? }) } + +async fn start_http( + server_config: ServerConfig, + preview_token: Arc>, + session: Session, +) -> Result<(), failure::Error> { + let server = tokio::spawn(server::http( + server_config, + Arc::clone(&preview_token), + session.host, + )); + + server.await? +} + +async fn start_https( + server_config: ServerConfig, + preview_token: Arc>, + session: Session, +) { + let mut server = tokio::spawn(server::https( + server_config.clone(), + Arc::clone(&preview_token), + session.host.clone(), + )); + + while server.await.is_ok() { + server = tokio::spawn(server::https( + server_config.clone(), + Arc::clone(&preview_token), + session.host.clone(), + )); + } +} diff --git a/src/commands/dev/edge/server.rs b/src/commands/dev/edge/server/http.rs similarity index 70% rename from src/commands/dev/edge/server.rs rename to src/commands/dev/edge/server/http.rs index b520d06c3..7925c64e0 100644 --- a/src/commands/dev/edge/server.rs +++ b/src/commands/dev/edge/server/http.rs @@ -1,3 +1,4 @@ +use super::preview_request; use crate::commands::dev::server_config::ServerConfig; use crate::commands::dev::utils::get_path_as_str; use crate::terminal::emoji; @@ -5,13 +6,11 @@ use crate::terminal::emoji; use std::sync::{Arc, Mutex}; use chrono::prelude::*; -use hyper::client::{HttpConnector, ResponseFuture}; -use hyper::header::{HeaderName, HeaderValue}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client as HyperClient, Request, Server}; use hyper_rustls::HttpsConnector; -pub(super) async fn serve( +pub async fn http( server_config: ServerConfig, preview_token: Arc>, host: String, @@ -44,6 +43,7 @@ pub(super) async fn serve( client, preview_token.to_owned(), host.clone(), + true, ) .await?; @@ -64,38 +64,10 @@ pub(super) async fn serve( let server = Server::bind(&listening_address).serve(make_service); println!("{} Listening on http://{}", emoji::EAR, listening_address); + if let Err(e) = server.await { - eprintln!("server error: {}", e) + eprintln!("{}", e); } - Ok(()) -} - -fn preview_request( - req: Request, - client: HyperClient>, - preview_token: String, - host: String, -) -> ResponseFuture { - let (mut parts, body) = req.into_parts(); - - let path = get_path_as_str(&parts.uri); - parts.headers.insert( - HeaderName::from_static("host"), - HeaderValue::from_str(&host).expect("Could not create host header"), - ); - - parts.headers.insert( - HeaderName::from_static("cf-workers-preview-token"), - HeaderValue::from_str(&preview_token).expect("Could not create token header"), - ); - - // TODO: figure out how to http _or_ https - parts.uri = format!("https://{}{}", host, path) - .parse() - .expect("Could not construct preview url"); - - let req = Request::from_parts(parts, body); - - client.request(req) + Ok(()) } diff --git a/src/commands/dev/edge/server/https.rs b/src/commands/dev/edge/server/https.rs new file mode 100644 index 000000000..6d66ed813 --- /dev/null +++ b/src/commands/dev/edge/server/https.rs @@ -0,0 +1,98 @@ +use super::preview_request; +use crate::commands::dev::server_config::ServerConfig; +use crate::commands::dev::tls; +use crate::commands::dev::utils::get_path_as_str; +use crate::terminal::{emoji, message}; + +use std::sync::{Arc, Mutex}; + +use chrono::prelude::*; +use futures_util::{ + future::TryFutureExt, + stream::{StreamExt, TryStreamExt}, +}; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Client as HyperClient, Request, Server}; +use hyper_rustls::HttpsConnector; +use tokio::net::TcpListener; + +pub async fn https( + server_config: ServerConfig, + preview_token: Arc>, + host: String, +) -> Result<(), failure::Error> { + tls::generate_cert()?; + + // set up https client to connect to the preview service + let https = HttpsConnector::new(); + let client = HyperClient::builder().build::<_, Body>(https); + + // create a closure that hyper will use later to handle HTTP requests + let service = make_service_fn(move |_| { + let client = client.to_owned(); + let preview_token = preview_token.to_owned(); + let host = host.to_owned(); + + async move { + Ok::<_, failure::Error>(service_fn(move |req| { + let client = client.to_owned(); + let preview_token = preview_token.lock().unwrap().to_owned(); + let host = host.to_owned(); + let version = req.version(); + let (parts, body) = req.into_parts(); + let req_method = parts.method.to_string(); + let now: DateTime = Local::now(); + let path = get_path_as_str(&parts.uri); + async move { + let resp = preview_request( + Request::from_parts(parts, body), + client, + preview_token.to_owned(), + host.clone(), + false, + ) + .await?; + + println!( + "[{}] {} {}{} {:?} {}", + now.format("%Y-%m-%d %H:%M:%S"), + req_method, + host, + path, + version, + resp.status() + ); + Ok::<_, failure::Error>(resp) + } + })) + } + }); + + let listening_address = server_config.listening_address; + + let mut tcp = TcpListener::bind(&listening_address).await?; + let tls_acceptor = tls::get_tls_acceptor()?; + let incoming_tls_stream = tcp + .incoming() + .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + .and_then(move |s| { + tls_acceptor + .accept(s) + .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + }) + .boxed(); + + let server = Server::builder(tls::HyperAcceptor { + acceptor: incoming_tls_stream, + }) + .serve(service); + + println!("{} Listening on https://{}", emoji::EAR, listening_address); + message::info("Generated certifiacte is not verified, browsers will give a warning and curl will require `--inscure`"); + + if let Err(e) = server.await { + eprintln!("{}", e); + } + + Ok(()) +} diff --git a/src/commands/dev/edge/server/mod.rs b/src/commands/dev/edge/server/mod.rs new file mode 100644 index 000000000..23bd6b04b --- /dev/null +++ b/src/commands/dev/edge/server/mod.rs @@ -0,0 +1,46 @@ +mod http; +mod https; + +pub use self::http::http; +pub use self::https::https; + +use crate::commands::dev::utils::get_path_as_str; + +use hyper::client::{HttpConnector, ResponseFuture}; +use hyper::header::{HeaderName, HeaderValue}; +use hyper::{Body, Client as HyperClient, Request}; +use hyper_rustls::HttpsConnector; + +fn preview_request( + req: Request, + client: HyperClient>, + preview_token: String, + host: String, + http: bool, +) -> ResponseFuture { + let (mut parts, body) = req.into_parts(); + + let path = get_path_as_str(&parts.uri); + + parts.headers.insert( + HeaderName::from_static("host"), + HeaderValue::from_str(&host).expect("Could not create host header"), + ); + + parts.headers.insert( + HeaderName::from_static("cf-workers-preview-token"), + HeaderValue::from_str(&preview_token).expect("Could not create token header"), + ); + + parts.uri = if http { + format!("http://{}{}", host, path) + } else { + format!("https://{}{}", host, path) + } + .parse() + .expect("Could not construct preview url"); + + let req = Request::from_parts(parts, body); + + client.request(req) +} diff --git a/src/commands/dev/gcs/mod.rs b/src/commands/dev/gcs/mod.rs index 613d11a8d..bf898c736 100644 --- a/src/commands/dev/gcs/mod.rs +++ b/src/commands/dev/gcs/mod.rs @@ -73,18 +73,11 @@ pub fn dev( // and we must block the main thread on the completion of // said futures runtime.block_on(async { - let mut running = true; let devtools_listener = tokio::spawn(socket::listen(socket_url.clone())); + let mut server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); - while running { - let server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); - - //let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); - let res = server.await; - running = match res { - Ok(_) => false, - Err(_) => true, - } + while server.await.is_ok() { + server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); } devtools_listener.await? diff --git a/src/commands/dev/gcs/server.rs b/src/commands/dev/gcs/server.rs index 771380ded..2ff1762d7 100644 --- a/src/commands/dev/gcs/server.rs +++ b/src/commands/dev/gcs/server.rs @@ -1,12 +1,16 @@ use crate::commands::dev::gcs::headers::{destructure_response, structure_request}; use crate::commands::dev::server_config::ServerConfig; -use crate::commands::dev::utils::get_path_as_str; use crate::commands::dev::tls; +use crate::commands::dev::utils::get_path_as_str; use crate::terminal::{emoji, message}; use std::sync::{Arc, Mutex}; use chrono::prelude::*; +use futures_util::{ + future::TryFutureExt, + stream::{StreamExt, TryStreamExt}, +}; use hyper::client::{HttpConnector, ResponseFuture}; use hyper::header::{HeaderName, HeaderValue}; use hyper::http::uri::InvalidUri; @@ -14,10 +18,6 @@ use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client as HyperClient, Request, Response, Server, Uri}; use hyper_rustls::HttpsConnector; use tokio::net::TcpListener; -use futures_util::{ - future::TryFutureExt, - stream::{StreamExt, TryStreamExt}, -}; const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com"; @@ -35,22 +35,6 @@ pub(super) async fn serve( let listening_address = server_config.listening_address; - // Create a TCP listener via tokio. - let mut tcp = TcpListener::bind(&listening_address).await?; - let tls_acceptor = tls::get_tls_acceptor()?; - // Prepare a long-running future stream to accept and serve cients. - - - let incoming_tls_stream = tcp - .incoming() - .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) - .and_then(move |s| { - tls_acceptor - .accept(s) - .map_err(|e| tls::io_error(format!("TLS Error: {:?}", e))) - }) - .boxed(); - // create a closure that hyper will use later to handle HTTP requests // this takes care of sending an incoming request along to // the uploaded Worker script and returning its response @@ -109,7 +93,23 @@ pub(super) async fn serve( } }); - let server = Server::builder(tls::HyperAcceptor{ acceptor: incoming_tls_stream }).serve(service); + // Create a TCP listener via tokio. + let mut tcp = TcpListener::bind(&listening_address).await?; + let tls_acceptor = tls::get_tls_acceptor()?; + let incoming_tls_stream = tcp + .incoming() + .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + .and_then(move |s| { + tls_acceptor + .accept(s) + .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + }) + .boxed(); + + let server = Server::builder(tls::HyperAcceptor { + acceptor: incoming_tls_stream, + }) + .serve(service); println!( "{} Listening on https://{}", emoji::EAR, @@ -118,7 +118,11 @@ pub(super) async fn serve( message::info("Generated certifiacte is not verified, browsers will give a warning and curl will require `--inscure`"); - Ok(server.await?) + if let Err(e) = server.await { + eprintln!("{}", e); + } + + Ok(()) } fn get_preview_url(path_string: &str) -> Result { diff --git a/src/commands/dev/mod.rs b/src/commands/dev/mod.rs index 13f51fa17..66d75186d 100644 --- a/src/commands/dev/mod.rs +++ b/src/commands/dev/mod.rs @@ -5,7 +5,7 @@ mod socket; mod tls; mod utils; -use server_config::ServerConfig; +pub use server_config::ServerConfig; use crate::build; use crate::settings::global_user::GlobalUser; @@ -17,20 +17,16 @@ pub fn dev( target: Target, deploy_config: DeployConfig, user: Option, - host: Option<&str>, - port: Option, - ip: Option<&str>, + server_config: ServerConfig, http: bool, verbose: bool, ) -> Result<(), failure::Error> { - let server_config = ServerConfig::new(host, ip, port)?; - // before serving requests we must first build the Worker build(&target)?; match user { // authenticated users connect to the edge - Some(user) => edge::dev(target, user, server_config, deploy_config, verbose), + Some(user) => edge::dev(target, user, server_config, deploy_config, http, verbose), // unauthenticated users connect to gcs None => { diff --git a/src/commands/dev/tls/mod.rs b/src/commands/dev/tls/mod.rs index 22b5f1a18..a8cd99462 100644 --- a/src/commands/dev/tls/mod.rs +++ b/src/commands/dev/tls/mod.rs @@ -6,10 +6,10 @@ use fs::File; use futures_util::stream::Stream; use rustls::internal::pemfile; use rustls::{NoClientAuth, ServerConfig}; +use std::path::PathBuf; use std::pin::Pin; use std::sync::Arc; use std::vec::Vec; -use std::path::PathBuf; use std::{fs, io}; use tokio::net::TcpStream; use tokio_rustls::{server::TlsStream, TlsAcceptor}; diff --git a/src/main.rs b/src/main.rs index 3d637cb57..9ff431a3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -793,7 +793,10 @@ fn run() -> Result<(), failure::Error> { let target = manifest.get_target(env, is_preview)?; let user = settings::global_user::GlobalUser::new().ok(); let verbose = matches.is_present("verbose"); - commands::dev::dev(target, deploy_config, user, host, port, ip, http, verbose)?; + + let server_config = commands::dev::ServerConfig::new(host, ip, port)?; + + commands::dev::dev(target, deploy_config, user, server_config, http, verbose)?; } else if matches.subcommand_matches("whoami").is_some() { log::info!("Getting User settings"); let user = settings::global_user::GlobalUser::new()?; From 9abd4a6ea989aa5ca64887e35ae74e654c982bb7 Mon Sep 17 00:00:00 2001 From: jspspike Date: Fri, 7 Aug 2020 16:06:18 -0500 Subject: [PATCH 4/8] Removed restarting server --- src/commands/dev/edge/mod.rs | 59 ++++++++------------------- src/commands/dev/edge/server/https.rs | 29 ++++++++----- src/commands/dev/gcs/mod.rs | 10 ++--- src/commands/dev/gcs/server.rs | 30 +++++++++----- 4 files changed, 60 insertions(+), 68 deletions(-) diff --git a/src/commands/dev/edge/mod.rs b/src/commands/dev/edge/mod.rs index 74d449145..19fd3938c 100644 --- a/src/commands/dev/edge/mod.rs +++ b/src/commands/dev/edge/mod.rs @@ -53,48 +53,25 @@ pub fn dev( let mut runtime = TokioRuntime::new()?; runtime.block_on(async { - let devtools_listener = tokio::spawn(socket::listen(session.clone().websocket_url)); - if http { - start_http(server_config, preview_token, session.clone()).await?; + let devtools_listener = tokio::spawn(socket::listen(session.websocket_url)); + let server = if http { + tokio::spawn(server::http( + server_config, + Arc::clone(&preview_token), + session.host, + )) } else { - start_https(server_config, preview_token, session.clone()).await; - } + tokio::spawn(server::https( + server_config.clone(), + Arc::clone(&preview_token), + session.host.clone(), + )) + }; - //let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); - devtools_listener.await? + let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); + match res { + Ok(_) => Ok(()), + Err(e) => Err(e), + } }) } - -async fn start_http( - server_config: ServerConfig, - preview_token: Arc>, - session: Session, -) -> Result<(), failure::Error> { - let server = tokio::spawn(server::http( - server_config, - Arc::clone(&preview_token), - session.host, - )); - - server.await? -} - -async fn start_https( - server_config: ServerConfig, - preview_token: Arc>, - session: Session, -) { - let mut server = tokio::spawn(server::https( - server_config.clone(), - Arc::clone(&preview_token), - session.host.clone(), - )); - - while server.await.is_ok() { - server = tokio::spawn(server::https( - server_config.clone(), - Arc::clone(&preview_token), - session.host.clone(), - )); - } -} diff --git a/src/commands/dev/edge/server/https.rs b/src/commands/dev/edge/server/https.rs index 6d66ed813..46ea9dd92 100644 --- a/src/commands/dev/edge/server/https.rs +++ b/src/commands/dev/edge/server/https.rs @@ -7,10 +7,7 @@ use crate::terminal::{emoji, message}; use std::sync::{Arc, Mutex}; use chrono::prelude::*; -use futures_util::{ - future::TryFutureExt, - stream::{StreamExt, TryStreamExt}, -}; +use futures_util::stream::StreamExt; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client as HyperClient, Request, Server}; use hyper_rustls::HttpsConnector; @@ -71,17 +68,27 @@ pub async fn https( let listening_address = server_config.listening_address; let mut tcp = TcpListener::bind(&listening_address).await?; - let tls_acceptor = tls::get_tls_acceptor()?; + let tls_acceptor = &tls::get_tls_acceptor()?; let incoming_tls_stream = tcp .incoming() - .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) - .and_then(move |s| { - tls_acceptor - .accept(s) - .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + .filter_map(move |s| async move { + let client = match s { + Ok(x) => x, + Err(e) => { + println!("Failed to accept client"); + return Some(Err(e)); + } + }; + match tls_acceptor.accept(client).await { + Ok(x) => Some(Ok(x)), + Err(e) => { + println!("Client connection error {}", e); + message::info("Make sure to use https and `--insecure` with curl"); + None + } + } }) .boxed(); - let server = Server::builder(tls::HyperAcceptor { acceptor: incoming_tls_stream, }) diff --git a/src/commands/dev/gcs/mod.rs b/src/commands/dev/gcs/mod.rs index bf898c736..a97ef4d94 100644 --- a/src/commands/dev/gcs/mod.rs +++ b/src/commands/dev/gcs/mod.rs @@ -74,13 +74,13 @@ pub fn dev( // said futures runtime.block_on(async { let devtools_listener = tokio::spawn(socket::listen(socket_url.clone())); - let mut server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); + let server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); - while server.await.is_ok() { - server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); + let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); + match res { + Ok(_) => Ok(()), + Err(e) => Err(e), } - - devtools_listener.await? }) } diff --git a/src/commands/dev/gcs/server.rs b/src/commands/dev/gcs/server.rs index 2ff1762d7..56acd5715 100644 --- a/src/commands/dev/gcs/server.rs +++ b/src/commands/dev/gcs/server.rs @@ -7,10 +7,7 @@ use crate::terminal::{emoji, message}; use std::sync::{Arc, Mutex}; use chrono::prelude::*; -use futures_util::{ - future::TryFutureExt, - stream::{StreamExt, TryStreamExt}, -}; +use futures_util::stream::StreamExt; use hyper::client::{HttpConnector, ResponseFuture}; use hyper::header::{HeaderName, HeaderValue}; use hyper::http::uri::InvalidUri; @@ -95,14 +92,25 @@ pub(super) async fn serve( // Create a TCP listener via tokio. let mut tcp = TcpListener::bind(&listening_address).await?; - let tls_acceptor = tls::get_tls_acceptor()?; + let tls_acceptor = &tls::get_tls_acceptor()?; let incoming_tls_stream = tcp .incoming() - .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) - .and_then(move |s| { - tls_acceptor - .accept(s) - .map_err(|e| tls::io_error(format!("Incoming connection failed: {:?}", e))) + .filter_map(move |s| async move { + let client = match s { + Ok(x) => x, + Err(e) => { + println!("Failed to accept client"); + return Some(Err(e)); + } + }; + match tls_acceptor.accept(client).await { + Ok(x) => Some(Ok(x)), + Err(e) => { + println!("Client connection error {}", e); + message::info("Make sure to use https and `--insecure` with curl"); + None + } + } }) .boxed(); @@ -116,7 +124,7 @@ pub(super) async fn serve( listening_address.to_string() ); - message::info("Generated certifiacte is not verified, browsers will give a warning and curl will require `--inscure`"); + message::info("Generated certificate is not verified, browsers will give a warning and curl will require `--inscure`"); if let Err(e) = server.await { eprintln!("{}", e); From 1809e15bcfcca7c60f0ba8a7f880e7ae17ad5c6a Mon Sep 17 00:00:00 2001 From: jspspike Date: Mon, 10 Aug 2020 14:44:16 -0500 Subject: [PATCH 5/8] Fixed issue with certifiacte generation --- src/commands/dev/gcs/server.rs | 4 +- src/commands/dev/tls/certs.rs | 108 ++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/src/commands/dev/gcs/server.rs b/src/commands/dev/gcs/server.rs index 56acd5715..48a46670e 100644 --- a/src/commands/dev/gcs/server.rs +++ b/src/commands/dev/gcs/server.rs @@ -99,8 +99,8 @@ pub(super) async fn serve( let client = match s { Ok(x) => x, Err(e) => { - println!("Failed to accept client"); - return Some(Err(e)); + println!("Failed to accept client {}", e); + return None; } }; match tls_acceptor.accept(client).await { diff --git a/src/commands/dev/tls/certs.rs b/src/commands/dev/tls/certs.rs index 3ee088e82..9a10f0e58 100644 --- a/src/commands/dev/tls/certs.rs +++ b/src/commands/dev/tls/certs.rs @@ -1,10 +1,13 @@ use openssl::asn1::Asn1Time; use openssl::bn::{BigNum, MsbOption}; use openssl::hash::MessageDigest; -use openssl::pkey::PKey; +use openssl::pkey::{PKey, Private}; use openssl::rsa::Rsa; -use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier}; -use openssl::x509::{X509NameBuilder, X509}; +use openssl::x509::extension::{ + AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName, + SubjectKeyIdentifier, +}; +use openssl::x509::{X509NameBuilder, X509Req, X509ReqBuilder, X509}; use std::fs; use std::path::PathBuf; @@ -29,22 +32,15 @@ fn create_output_files() -> Result, failure::Error> { } /// Generate cert and private key for local https server -pub fn generate_cert() -> Result<(), failure::Error> { - let files = create_output_files()?; - if files.is_none() { - return Ok(()); - } - - let (cert_file, priv_file) = files.unwrap(); - +fn create_ca() -> Result<(X509, PKey), failure::Error> { let rsa = Rsa::generate(2048)?; let privkey = PKey::from_rsa(rsa)?; let mut x509_name = X509NameBuilder::new()?; x509_name.append_entry_by_text("C", "US")?; x509_name.append_entry_by_text("ST", "TX")?; - x509_name.append_entry_by_text("O", "Example")?; - x509_name.append_entry_by_text("CN", "example")?; + x509_name.append_entry_by_text("O", "Wrangler")?; + x509_name.append_entry_by_text("CN", "Wrangler")?; let x509_name = x509_name.build(); let mut cert_builder = X509::builder()?; @@ -77,8 +73,92 @@ pub fn generate_cert() -> Result<(), failure::Error> { cert_builder.append_extension(subject_key_identifier)?; cert_builder.sign(&privkey, MessageDigest::sha256())?; - let cert_str = cert_builder.build().to_pem().unwrap(); + let cert = cert_builder.build(); + + Ok((cert, privkey)) +} + +fn create_req(privkey: &PKey) -> Result { + let mut req_builder = X509ReqBuilder::new()?; + req_builder.set_pubkey(&privkey)?; + + let mut x509_name = X509NameBuilder::new()?; + x509_name.append_entry_by_text("C", "US")?; + x509_name.append_entry_by_text("ST", "TX")?; + x509_name.append_entry_by_text("O", "Some organization")?; + x509_name.append_entry_by_text("CN", "www.example.com")?; + let x509_name = x509_name.build(); + req_builder.set_subject_name(&x509_name)?; + + req_builder.sign(&privkey, MessageDigest::sha256())?; + let req = req_builder.build(); + Ok(req) +} + +pub fn generate_cert() -> Result<(), failure::Error> { + let files = create_output_files()?; + if files.is_none() { + return Ok(()); + } + + let (cert_file, priv_file) = files.unwrap(); + + let (ca, ca_key) = create_ca()?; + + let rsa = Rsa::generate(2048)?; + let privkey = PKey::from_rsa(rsa)?; + + let req = create_req(&privkey)?; + let rsa = Rsa::generate(2048)?; + let privkey = PKey::from_rsa(rsa)?; + + let mut cert_builder = X509::builder()?; + cert_builder.set_version(2)?; + let serial_number = { + let mut serial = BigNum::new()?; + serial.rand(159, MsbOption::MAYBE_ZERO, false)?; + serial.to_asn1_integer()? + }; + cert_builder.set_serial_number(&serial_number)?; + cert_builder.set_subject_name(req.subject_name())?; + cert_builder.set_issuer_name(ca.subject_name())?; + cert_builder.set_pubkey(&privkey)?; + let not_before = Asn1Time::days_from_now(0)?; + cert_builder.set_not_before(¬_before)?; + let not_after = Asn1Time::days_from_now(365)?; + cert_builder.set_not_after(¬_after)?; + + cert_builder.append_extension(BasicConstraints::new().build()?)?; + + cert_builder.append_extension( + KeyUsage::new() + .critical() + .non_repudiation() + .digital_signature() + .key_encipherment() + .build()?, + )?; + + let subject_key_identifier = + SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(&ca), None))?; + cert_builder.append_extension(subject_key_identifier)?; + + let auth_key_identifier = AuthorityKeyIdentifier::new() + .keyid(false) + .issuer(false) + .build(&cert_builder.x509v3_context(Some(&ca), None))?; + cert_builder.append_extension(auth_key_identifier)?; + + let subject_alt_name = SubjectAlternativeName::new() + .dns("*.example.com") + .dns("hello.com") + .build(&cert_builder.x509v3_context(Some(&ca), None))?; + cert_builder.append_extension(subject_alt_name)?; + + cert_builder.sign(&ca_key, MessageDigest::sha256())?; + + let cert_str = cert_builder.build().to_pem().unwrap(); let priv_str = privkey.private_key_to_pem_pkcs8().unwrap(); fs::write(cert_file, cert_str)?; From 4a4b8398a95efc943df56adef91f2d458c6e4bf0 Mon Sep 17 00:00:00 2001 From: jspspike Date: Tue, 11 Aug 2020 19:43:14 -0500 Subject: [PATCH 6/8] Added upstream-http and local-https --- src/commands/dev/edge/mod.rs | 18 ++-- src/commands/dev/edge/server/http.rs | 3 +- src/commands/dev/edge/server/https.rs | 8 +- src/commands/dev/gcs/mod.rs | 12 ++- src/commands/dev/gcs/server/http.rs | 94 +++++++++++++++++++ .../dev/gcs/{server.rs => server/https.rs} | 51 ++-------- src/commands/dev/gcs/server/mod.rs | 49 ++++++++++ src/commands/dev/mod.rs | 27 ++++-- src/commands/dev/server_config/mod.rs | 11 ++- src/commands/dev/tls/certs.rs | 4 +- src/main.rs | 37 ++++++-- src/settings/toml/dev.rs | 3 +- 12 files changed, 235 insertions(+), 82 deletions(-) create mode 100644 src/commands/dev/gcs/server/http.rs rename src/commands/dev/gcs/{server.rs => server/https.rs} (75%) create mode 100644 src/commands/dev/gcs/server/mod.rs diff --git a/src/commands/dev/edge/mod.rs b/src/commands/dev/edge/mod.rs index 19fd3938c..b1388cb58 100644 --- a/src/commands/dev/edge/mod.rs +++ b/src/commands/dev/edge/mod.rs @@ -19,7 +19,8 @@ pub fn dev( user: GlobalUser, server_config: ServerConfig, deploy_config: DeployConfig, - http: bool, + local_https: bool, + upstream_http: bool, verbose: bool, ) -> Result<(), failure::Error> { let session = Session::new(&target, &user, &deploy_config)?; @@ -54,18 +55,19 @@ pub fn dev( let mut runtime = TokioRuntime::new()?; runtime.block_on(async { let devtools_listener = tokio::spawn(socket::listen(session.websocket_url)); - let server = if http { - tokio::spawn(server::http( - server_config, - Arc::clone(&preview_token), - session.host, - )) - } else { + let server = if local_https { tokio::spawn(server::https( server_config.clone(), Arc::clone(&preview_token), session.host.clone(), )) + } else { + tokio::spawn(server::http( + server_config, + Arc::clone(&preview_token), + session.host, + upstream_http, + )) }; let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); diff --git a/src/commands/dev/edge/server/http.rs b/src/commands/dev/edge/server/http.rs index 7925c64e0..bb52c9894 100644 --- a/src/commands/dev/edge/server/http.rs +++ b/src/commands/dev/edge/server/http.rs @@ -14,6 +14,7 @@ pub async fn http( server_config: ServerConfig, preview_token: Arc>, host: String, + upstream_http: bool, ) -> Result<(), failure::Error> { // set up https client to connect to the preview service let https = HttpsConnector::new(); @@ -43,7 +44,7 @@ pub async fn http( client, preview_token.to_owned(), host.clone(), - true, + upstream_http, ) .await?; diff --git a/src/commands/dev/edge/server/https.rs b/src/commands/dev/edge/server/https.rs index 46ea9dd92..bfef386e0 100644 --- a/src/commands/dev/edge/server/https.rs +++ b/src/commands/dev/edge/server/https.rs @@ -75,14 +75,14 @@ pub async fn https( let client = match s { Ok(x) => x, Err(e) => { - println!("Failed to accept client"); - return Some(Err(e)); + eprintln!("Failed to accept client {}", e); + return None; } }; match tls_acceptor.accept(client).await { Ok(x) => Some(Ok(x)), Err(e) => { - println!("Client connection error {}", e); + eprintln!("Client connection error {}", e); message::info("Make sure to use https and `--insecure` with curl"); None } @@ -95,7 +95,7 @@ pub async fn https( .serve(service); println!("{} Listening on https://{}", emoji::EAR, listening_address); - message::info("Generated certifiacte is not verified, browsers will give a warning and curl will require `--inscure`"); + message::info("Generated certificate is not verified, browsers will give a warning and curl will require `--insecure`"); if let Err(e) = server.await { eprintln!("{}", e); diff --git a/src/commands/dev/gcs/mod.rs b/src/commands/dev/gcs/mod.rs index a97ef4d94..c1f0b321c 100644 --- a/src/commands/dev/gcs/mod.rs +++ b/src/commands/dev/gcs/mod.rs @@ -3,7 +3,6 @@ mod server; mod setup; mod watch; -use server::serve; use setup::{get_preview_id, get_session_id}; use watch::watch_for_changes; @@ -20,6 +19,7 @@ use url::Url; pub fn dev( target: Target, server_config: ServerConfig, + local_https: bool, verbose: bool, ) -> Result<(), failure::Error> { println!("unauthenticated"); @@ -74,7 +74,15 @@ pub fn dev( // said futures runtime.block_on(async { let devtools_listener = tokio::spawn(socket::listen(socket_url.clone())); - let server = tokio::spawn(serve(server_config.clone(), Arc::clone(&preview_id))); + + let server = if local_https { + tokio::spawn(server::https( + server_config.clone(), + Arc::clone(&preview_id), + )) + } else { + tokio::spawn(server::http(server_config.clone(), Arc::clone(&preview_id))) + }; let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); match res { diff --git a/src/commands/dev/gcs/server/http.rs b/src/commands/dev/gcs/server/http.rs new file mode 100644 index 000000000..cc26b63c2 --- /dev/null +++ b/src/commands/dev/gcs/server/http.rs @@ -0,0 +1,94 @@ +use super::preview_request; +use crate::commands::dev::gcs::headers::destructure_response; +use crate::commands::dev::server_config::ServerConfig; +use crate::commands::dev::utils::get_path_as_str; +use crate::terminal::emoji; + +use std::sync::{Arc, Mutex}; + +use chrono::prelude::*; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Client as HyperClient, Request, Response, Server}; +use hyper_rustls::HttpsConnector; + +/// performs all logic that takes an incoming request +/// and routes it to the Workers runtime preview service +pub async fn http( + server_config: ServerConfig, + preview_id: Arc>, +) -> Result<(), failure::Error> { + // set up https client to connect to the preview service + let https = HttpsConnector::new(); + let client = HyperClient::builder().build::<_, Body>(https); + + let listening_address = server_config.listening_address; + + // create a closure that hyper will use later to handle HTTP requests + // this takes care of sending an incoming request along to + // the uploaded Worker script and returning its response + let make_service = make_service_fn(move |_| { + let client = client.to_owned(); + let server_config = server_config.to_owned(); + let preview_id = preview_id.to_owned(); + async move { + Ok::<_, failure::Error>(service_fn(move |req| { + let client = client.to_owned(); + let server_config = server_config.to_owned(); + let preview_id = preview_id.lock().unwrap().to_owned(); + let version = req.version(); + + // record the time of the request + let now: DateTime = Local::now(); + + // split the request into parts so we can read + // what it contains and display in logs + let (parts, body) = req.into_parts(); + + let req_method = parts.method.to_string(); + + // parse the path so we can send it to the preview service + // we don't want to send "localhost:8787/path", just "/path" + let path = get_path_as_str(&parts.uri); + + async move { + // send the request to the preview service + let resp = preview_request( + Request::from_parts(parts, body), + client, + preview_id.to_owned(), + ) + .await?; + let (mut parts, body) = resp.into_parts(); + + // format the response for the user + destructure_response(&mut parts)?; + let resp = Response::from_parts(parts, body); + + // print information about the response + // [2020-04-20 15:25:54] GET example.com/ HTTP/1.1 200 OK + println!( + "[{}] {} {}{} {:?} {}", + now.format("%Y-%m-%d %H:%M:%S"), + req_method, + server_config.host, + path, + version, + resp.status() + ); + Ok::<_, failure::Error>(resp) + } + })) + } + }); + + let server = Server::bind(&listening_address).serve(make_service); + println!( + "{} Listening on http://{}", + emoji::EAR, + listening_address.to_string() + ); + if let Err(e) = server.await { + eprintln!("server error: {}", e); + } + Ok(()) +} diff --git a/src/commands/dev/gcs/server.rs b/src/commands/dev/gcs/server/https.rs similarity index 75% rename from src/commands/dev/gcs/server.rs rename to src/commands/dev/gcs/server/https.rs index 48a46670e..aaf6f36f3 100644 --- a/src/commands/dev/gcs/server.rs +++ b/src/commands/dev/gcs/server/https.rs @@ -1,4 +1,5 @@ -use crate::commands::dev::gcs::headers::{destructure_response, structure_request}; +use super::preview_request; +use crate::commands::dev::gcs::headers::destructure_response; use crate::commands::dev::server_config::ServerConfig; use crate::commands::dev::tls; use crate::commands::dev::utils::get_path_as_str; @@ -8,19 +9,14 @@ use std::sync::{Arc, Mutex}; use chrono::prelude::*; use futures_util::stream::StreamExt; -use hyper::client::{HttpConnector, ResponseFuture}; -use hyper::header::{HeaderName, HeaderValue}; -use hyper::http::uri::InvalidUri; use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Client as HyperClient, Request, Response, Server, Uri}; +use hyper::{Body, Client as HyperClient, Request, Response, Server}; use hyper_rustls::HttpsConnector; use tokio::net::TcpListener; -const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com"; - /// performs all logic that takes an incoming request /// and routes it to the Workers runtime preview service -pub(super) async fn serve( +pub async fn https( server_config: ServerConfig, preview_id: Arc>, ) -> Result<(), failure::Error> { @@ -99,14 +95,14 @@ pub(super) async fn serve( let client = match s { Ok(x) => x, Err(e) => { - println!("Failed to accept client {}", e); + eprintln!("Failed to accept client {}", e); return None; } }; match tls_acceptor.accept(client).await { Ok(x) => Some(Ok(x)), Err(e) => { - println!("Client connection error {}", e); + eprintln!("Client connection error {}", e); message::info("Make sure to use https and `--insecure` with curl"); None } @@ -124,7 +120,7 @@ pub(super) async fn serve( listening_address.to_string() ); - message::info("Generated certificate is not verified, browsers will give a warning and curl will require `--inscure`"); + message::info("Generated certificate is not verified, browsers will give a warning and curl will require `--insecure`"); if let Err(e) = server.await { eprintln!("{}", e); @@ -132,36 +128,3 @@ pub(super) async fn serve( Ok(()) } - -fn get_preview_url(path_string: &str) -> Result { - format!("https://{}{}", PREVIEW_HOST, path_string).parse() -} - -fn preview_request( - req: Request, - client: HyperClient>, - preview_id: String, -) -> ResponseFuture { - let (mut parts, body) = req.into_parts(); - - let path = get_path_as_str(&parts.uri); - let preview_id = &preview_id; - - structure_request(&mut parts); - - parts.headers.insert( - HeaderName::from_static("host"), - HeaderValue::from_static(PREVIEW_HOST), - ); - - parts.headers.insert( - HeaderName::from_static("cf-ew-preview"), - HeaderValue::from_str(preview_id).expect("Could not create header for preview id"), - ); - - parts.uri = get_preview_url(&path).expect("Could not get preview url"); - - let req = Request::from_parts(parts, body); - - client.request(req) -} diff --git a/src/commands/dev/gcs/server/mod.rs b/src/commands/dev/gcs/server/mod.rs new file mode 100644 index 000000000..e991a80a0 --- /dev/null +++ b/src/commands/dev/gcs/server/mod.rs @@ -0,0 +1,49 @@ +mod http; +mod https; + +pub use self::http::http; +pub use self::https::https; + +use crate::commands::dev::gcs::headers::structure_request; +use crate::commands::dev::utils::get_path_as_str; + +use hyper::client::{HttpConnector, ResponseFuture}; +use hyper::header::{HeaderName, HeaderValue}; +use hyper::http::uri::InvalidUri; +use hyper::{Body, Client as HyperClient, Request, Uri}; +use hyper_rustls::HttpsConnector; + +const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com"; + +fn get_preview_url(path_string: &str) -> Result { + format!("https://{}{}", PREVIEW_HOST, path_string).parse() +} + +pub fn preview_request( + req: Request, + client: HyperClient>, + preview_id: String, +) -> ResponseFuture { + let (mut parts, body) = req.into_parts(); + + let path = get_path_as_str(&parts.uri); + let preview_id = &preview_id; + + structure_request(&mut parts); + + parts.headers.insert( + HeaderName::from_static("host"), + HeaderValue::from_static(PREVIEW_HOST), + ); + + parts.headers.insert( + HeaderName::from_static("cf-ew-preview"), + HeaderValue::from_str(preview_id).expect("Could not create header for preview id"), + ); + + parts.uri = get_preview_url(&path).expect("Could not get preview url"); + + let req = Request::from_parts(parts, body); + + client.request(req) +} diff --git a/src/commands/dev/mod.rs b/src/commands/dev/mod.rs index 66d75186d..fe41d0114 100644 --- a/src/commands/dev/mod.rs +++ b/src/commands/dev/mod.rs @@ -18,23 +18,32 @@ pub fn dev( deploy_config: DeployConfig, user: Option, server_config: ServerConfig, - http: bool, + local_https: bool, + upstream_http: bool, verbose: bool, ) -> Result<(), failure::Error> { // before serving requests we must first build the Worker build(&target)?; + if server_config.host.is_https() && upstream_http { + failure::bail!("Can't upstream http with https host") + } else if local_https && upstream_http { + failure::bail!("Can't have local https and upstreamed http") + } + match user { // authenticated users connect to the edge - Some(user) => edge::dev(target, user, server_config, deploy_config, http, verbose), + Some(user) => edge::dev( + target, + user, + server_config, + deploy_config, + local_https, + upstream_http, + verbose, + ), // unauthenticated users connect to gcs - None => { - if http { - failure::bail!("Unauthenticated dev must use https") - } else { - gcs::dev(target, server_config, verbose) - } - } + None => gcs::dev(target, server_config, local_https, verbose), } } diff --git a/src/commands/dev/server_config/mod.rs b/src/commands/dev/server_config/mod.rs index b9b836b98..df3ae09de 100644 --- a/src/commands/dev/server_config/mod.rs +++ b/src/commands/dev/server_config/mod.rs @@ -15,6 +15,7 @@ impl ServerConfig { host: Option<&str>, ip: Option<&str>, port: Option, + upstream_http: bool, ) -> Result { let ip = ip.unwrap_or("127.0.0.1"); let port = port.unwrap_or(8787); @@ -23,9 +24,13 @@ impl ServerConfig { Ok(socket) => socket.local_addr(), Err(_) => failure::bail!("{} is unavailable, try binding to another address with the --port and --ip flags, or stop other `wrangler dev` processes.", &addr) }?; - let host = host - .unwrap_or("https://tutorial.cloudflareworkers.com") - .to_string(); + let host = if upstream_http { + host.unwrap_or("http://tutorial.cloudflareworkers.com") + .to_string() + } else { + host.unwrap_or("https://tutorial.cloudflareworkers.com") + .to_string() + }; let host = Host::new(&host)?; diff --git a/src/commands/dev/tls/certs.rs b/src/commands/dev/tls/certs.rs index 9a10f0e58..1f89cc46d 100644 --- a/src/commands/dev/tls/certs.rs +++ b/src/commands/dev/tls/certs.rs @@ -31,7 +31,7 @@ fn create_output_files() -> Result, failure::Error> { } } -/// Generate cert and private key for local https server +/// Generate certificate authority to sign cert fn create_ca() -> Result<(X509, PKey), failure::Error> { let rsa = Rsa::generate(2048)?; let privkey = PKey::from_rsa(rsa)?; @@ -95,6 +95,7 @@ fn create_req(privkey: &PKey) -> Result { Ok(req) } +/// Generate cert and private key pub fn generate_cert() -> Result<(), failure::Error> { let files = create_output_files()?; if files.is_none() { @@ -132,6 +133,7 @@ pub fn generate_cert() -> Result<(), failure::Error> { cert_builder.append_extension(BasicConstraints::new().build()?)?; cert_builder.append_extension( + //Extensions requried by browsers to be a valid cert KeyUsage::new() .critical() .non_repudiation() diff --git a/src/main.rs b/src/main.rs index 9ff431a3d..9ed5358e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -522,9 +522,15 @@ fn run() -> Result<(), failure::Error> { .takes_value(true) ) .arg( - Arg::with_name("http") - .help("Runs local server on http instead of https, only valid when running dev authenticated") - .long("http") + Arg::with_name("local-https") + .help("Takes requests for local server via https instead of http") + .long("local-https") + .takes_value(false) + ) + .arg( + Arg::with_name("upstream-http") + .help("Sends requests to host via http instead of https") + .long("upstream-http") .takes_value(false) ) .arg(verbose_arg.clone()) @@ -770,7 +776,8 @@ fn run() -> Result<(), failure::Error> { let mut port: Option = matches .value_of("port") .map(|p| p.parse().expect("--port expects a number")); - let mut http: bool = matches.is_present("http"); + let mut local_https: bool = matches.is_present("local-https"); + let mut upstream_http: bool = matches.is_present("upstream-http"); // Check if arg not given but present in wrangler.toml if let Some(d) = &manifest.dev { @@ -782,8 +789,12 @@ fn run() -> Result<(), failure::Error> { port = d.port; } - if !http && d.http.is_some() { - http = d.http.unwrap(); + if !local_https && d.local_https.is_some() { + local_https = d.local_https.unwrap(); + } + + if !upstream_http && d.upstream_http.is_some() { + upstream_http = d.upstream_http.unwrap(); } } @@ -794,9 +805,17 @@ fn run() -> Result<(), failure::Error> { let user = settings::global_user::GlobalUser::new().ok(); let verbose = matches.is_present("verbose"); - let server_config = commands::dev::ServerConfig::new(host, ip, port)?; - - commands::dev::dev(target, deploy_config, user, server_config, http, verbose)?; + let server_config = commands::dev::ServerConfig::new(host, ip, port, upstream_http)?; + + commands::dev::dev( + target, + deploy_config, + user, + server_config, + local_https, + upstream_http, + verbose, + )?; } else if matches.subcommand_matches("whoami").is_some() { log::info!("Getting User settings"); let user = settings::global_user::GlobalUser::new()?; diff --git a/src/settings/toml/dev.rs b/src/settings/toml/dev.rs index 7346b1c3a..ffcef2342 100644 --- a/src/settings/toml/dev.rs +++ b/src/settings/toml/dev.rs @@ -5,5 +5,6 @@ use serde::{Deserialize, Serialize}; pub struct Dev { pub ip: Option, pub port: Option, - pub http: Option, + pub local_https: Option, + pub upstream_http: Option, } From b793658b0e593095219a03ad146671b3a87a9a71 Mon Sep 17 00:00:00 2001 From: jspspike Date: Wed, 12 Aug 2020 13:06:03 -0500 Subject: [PATCH 7/8] Changed dev args from bool to string --- src/commands/dev/edge/mod.rs | 19 ++++++------ src/commands/dev/edge/server/http.rs | 6 ++-- src/commands/dev/edge/server/https.rs | 5 ++-- src/commands/dev/edge/server/mod.rs | 10 +++---- src/commands/dev/gcs/mod.rs | 15 +++++----- src/commands/dev/mod.rs | 27 +++++++++++------ src/commands/dev/server_config/mod.rs | 18 ++++++----- src/commands/dev/server_config/protocol.rs | 35 ++++++++++++++++++++++ src/commands/dev/tls/certs.rs | 2 +- src/main.rs | 32 ++++++++++++-------- src/settings/toml/dev.rs | 4 +-- 11 files changed, 113 insertions(+), 60 deletions(-) create mode 100644 src/commands/dev/server_config/protocol.rs diff --git a/src/commands/dev/edge/mod.rs b/src/commands/dev/edge/mod.rs index b1388cb58..d5d3444e1 100644 --- a/src/commands/dev/edge/mod.rs +++ b/src/commands/dev/edge/mod.rs @@ -5,7 +5,7 @@ mod watch; use setup::{upload, Session}; use watch::watch_for_changes; -use crate::commands::dev::{socket, ServerConfig}; +use crate::commands::dev::{socket, Protocol, ServerConfig}; use crate::settings::global_user::GlobalUser; use crate::settings::toml::{DeployConfig, Target}; @@ -19,8 +19,8 @@ pub fn dev( user: GlobalUser, server_config: ServerConfig, deploy_config: DeployConfig, - local_https: bool, - upstream_http: bool, + local_protocol: Protocol, + upstream_protocol: Protocol, verbose: bool, ) -> Result<(), failure::Error> { let session = Session::new(&target, &user, &deploy_config)?; @@ -55,19 +55,18 @@ pub fn dev( let mut runtime = TokioRuntime::new()?; runtime.block_on(async { let devtools_listener = tokio::spawn(socket::listen(session.websocket_url)); - let server = if local_https { - tokio::spawn(server::https( + let server = match local_protocol { + Protocol::Https => tokio::spawn(server::https( server_config.clone(), Arc::clone(&preview_token), session.host.clone(), - )) - } else { - tokio::spawn(server::http( + )), + Protocol::Http => tokio::spawn(server::http( server_config, Arc::clone(&preview_token), session.host, - upstream_http, - )) + upstream_protocol, + )), }; let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); diff --git a/src/commands/dev/edge/server/http.rs b/src/commands/dev/edge/server/http.rs index bb52c9894..80a961ffa 100644 --- a/src/commands/dev/edge/server/http.rs +++ b/src/commands/dev/edge/server/http.rs @@ -1,6 +1,6 @@ use super::preview_request; -use crate::commands::dev::server_config::ServerConfig; use crate::commands::dev::utils::get_path_as_str; +use crate::commands::dev::{Protocol, ServerConfig}; use crate::terminal::emoji; use std::sync::{Arc, Mutex}; @@ -14,7 +14,7 @@ pub async fn http( server_config: ServerConfig, preview_token: Arc>, host: String, - upstream_http: bool, + upstream_protocol: Protocol, ) -> Result<(), failure::Error> { // set up https client to connect to the preview service let https = HttpsConnector::new(); @@ -44,7 +44,7 @@ pub async fn http( client, preview_token.to_owned(), host.clone(), - upstream_http, + upstream_protocol, ) .await?; diff --git a/src/commands/dev/edge/server/https.rs b/src/commands/dev/edge/server/https.rs index bfef386e0..601b288d9 100644 --- a/src/commands/dev/edge/server/https.rs +++ b/src/commands/dev/edge/server/https.rs @@ -1,7 +1,6 @@ use super::preview_request; -use crate::commands::dev::server_config::ServerConfig; -use crate::commands::dev::tls; use crate::commands::dev::utils::get_path_as_str; +use crate::commands::dev::{tls, Protocol, ServerConfig}; use crate::terminal::{emoji, message}; use std::sync::{Arc, Mutex}; @@ -46,7 +45,7 @@ pub async fn https( client, preview_token.to_owned(), host.clone(), - false, + Protocol::Https, ) .await?; diff --git a/src/commands/dev/edge/server/mod.rs b/src/commands/dev/edge/server/mod.rs index 23bd6b04b..a9233a3a6 100644 --- a/src/commands/dev/edge/server/mod.rs +++ b/src/commands/dev/edge/server/mod.rs @@ -5,6 +5,7 @@ pub use self::http::http; pub use self::https::https; use crate::commands::dev::utils::get_path_as_str; +use crate::commands::dev::Protocol; use hyper::client::{HttpConnector, ResponseFuture}; use hyper::header::{HeaderName, HeaderValue}; @@ -16,7 +17,7 @@ fn preview_request( client: HyperClient>, preview_token: String, host: String, - http: bool, + http: Protocol, ) -> ResponseFuture { let (mut parts, body) = req.into_parts(); @@ -32,10 +33,9 @@ fn preview_request( HeaderValue::from_str(&preview_token).expect("Could not create token header"), ); - parts.uri = if http { - format!("http://{}{}", host, path) - } else { - format!("https://{}{}", host, path) + parts.uri = match http { + Protocol::Http => format!("http://{}{}", host, path), + Protocol::Https => format!("https://{}{}", host, path), } .parse() .expect("Could not construct preview url"); diff --git a/src/commands/dev/gcs/mod.rs b/src/commands/dev/gcs/mod.rs index c1f0b321c..de59f452e 100644 --- a/src/commands/dev/gcs/mod.rs +++ b/src/commands/dev/gcs/mod.rs @@ -6,7 +6,7 @@ mod watch; use setup::{get_preview_id, get_session_id}; use watch::watch_for_changes; -use crate::commands::dev::{socket, ServerConfig}; +use crate::commands::dev::{socket, Protocol, ServerConfig}; use crate::settings::toml::Target; use std::sync::{Arc, Mutex}; @@ -19,7 +19,7 @@ use url::Url; pub fn dev( target: Target, server_config: ServerConfig, - local_https: bool, + local_protocol: Protocol, verbose: bool, ) -> Result<(), failure::Error> { println!("unauthenticated"); @@ -75,13 +75,14 @@ pub fn dev( runtime.block_on(async { let devtools_listener = tokio::spawn(socket::listen(socket_url.clone())); - let server = if local_https { - tokio::spawn(server::https( + let server = match local_protocol { + Protocol::Https => tokio::spawn(server::https( server_config.clone(), Arc::clone(&preview_id), - )) - } else { - tokio::spawn(server::http(server_config.clone(), Arc::clone(&preview_id))) + )), + Protocol::Http => { + tokio::spawn(server::http(server_config.clone(), Arc::clone(&preview_id))) + } }; let res = tokio::try_join!(async { devtools_listener.await? }, async { server.await? }); diff --git a/src/commands/dev/mod.rs b/src/commands/dev/mod.rs index fe41d0114..e3a3ea8db 100644 --- a/src/commands/dev/mod.rs +++ b/src/commands/dev/mod.rs @@ -5,11 +5,13 @@ mod socket; mod tls; mod utils; +pub use server_config::Protocol; pub use server_config::ServerConfig; use crate::build; use crate::settings::global_user::GlobalUser; use crate::settings::toml::{DeployConfig, Target}; +use crate::terminal::styles; /// `wrangler dev` starts a server on a dev machine that routes incoming HTTP requests /// to a Cloudflare Workers runtime and returns HTTP responses @@ -18,17 +20,24 @@ pub fn dev( deploy_config: DeployConfig, user: Option, server_config: ServerConfig, - local_https: bool, - upstream_http: bool, + local_protocol: Protocol, + upstream_protocol: Protocol, verbose: bool, ) -> Result<(), failure::Error> { // before serving requests we must first build the Worker build(&target)?; - if server_config.host.is_https() && upstream_http { - failure::bail!("Can't upstream http with https host") - } else if local_https && upstream_http { - failure::bail!("Can't have local https and upstreamed http") + let host_str = styles::highlight("--host"); + let local_str = styles::highlight("--local-protocol"); + let upstream_str = styles::highlight("--upstream-protocol"); + + if server_config.host.is_https() != upstream_protocol.is_https() { + failure::bail!(format!( + "Protocol mismatch: protocol in {} and protocol in {} must match", + host_str, upstream_str + )) + } else if local_protocol.is_https() && upstream_protocol.is_http() { + failure::bail!("{} cannot be https if {} is http", local_str, upstream_str) } match user { @@ -38,12 +47,12 @@ pub fn dev( user, server_config, deploy_config, - local_https, - upstream_http, + local_protocol, + upstream_protocol, verbose, ), // unauthenticated users connect to gcs - None => gcs::dev(target, server_config, local_https, verbose), + None => gcs::dev(target, server_config, local_protocol, verbose), } } diff --git a/src/commands/dev/server_config/mod.rs b/src/commands/dev/server_config/mod.rs index df3ae09de..fe6f06a3e 100644 --- a/src/commands/dev/server_config/mod.rs +++ b/src/commands/dev/server_config/mod.rs @@ -1,4 +1,7 @@ mod host; +mod protocol; + +pub use protocol::Protocol; use host::Host; @@ -15,7 +18,7 @@ impl ServerConfig { host: Option<&str>, ip: Option<&str>, port: Option, - upstream_http: bool, + upstream_protocol: Protocol, ) -> Result { let ip = ip.unwrap_or("127.0.0.1"); let port = port.unwrap_or(8787); @@ -24,12 +27,13 @@ impl ServerConfig { Ok(socket) => socket.local_addr(), Err(_) => failure::bail!("{} is unavailable, try binding to another address with the --port and --ip flags, or stop other `wrangler dev` processes.", &addr) }?; - let host = if upstream_http { - host.unwrap_or("http://tutorial.cloudflareworkers.com") - .to_string() - } else { - host.unwrap_or("https://tutorial.cloudflareworkers.com") - .to_string() + let host = match upstream_protocol { + Protocol::Http => host + .unwrap_or("http://tutorial.cloudflareworkers.com") + .to_string(), + Protocol::Https => host + .unwrap_or("https://tutorial.cloudflareworkers.com") + .to_string(), }; let host = Host::new(&host)?; diff --git a/src/commands/dev/server_config/protocol.rs b/src/commands/dev/server_config/protocol.rs new file mode 100644 index 000000000..71146dfdb --- /dev/null +++ b/src/commands/dev/server_config/protocol.rs @@ -0,0 +1,35 @@ +pub use std::convert::TryFrom; + +#[derive(Clone, Copy)] +pub enum Protocol { + Http, + Https, +} + +impl Protocol { + pub fn is_http(&self) -> bool { + match self { + Protocol::Http => true, + _ => false, + } + } + + pub fn is_https(&self) -> bool { + match self { + Protocol::Https => true, + _ => false, + } + } +} + +impl TryFrom<&str> for Protocol { + type Error = failure::Error; + + fn try_from(p: &str) -> Result { + match p { + "http" => Ok(Protocol::Http), + "https" => Ok(Protocol::Https), + _ => failure::bail!("Invalid protocol, must be http or https"), + } + } +} diff --git a/src/commands/dev/tls/certs.rs b/src/commands/dev/tls/certs.rs index 1f89cc46d..654a53371 100644 --- a/src/commands/dev/tls/certs.rs +++ b/src/commands/dev/tls/certs.rs @@ -133,7 +133,7 @@ pub fn generate_cert() -> Result<(), failure::Error> { cert_builder.append_extension(BasicConstraints::new().build()?)?; cert_builder.append_extension( - //Extensions requried by browsers to be a valid cert + // Extensions requried by browsers to be a valid cert KeyUsage::new() .critical() .non_repudiation() diff --git a/src/main.rs b/src/main.rs index 9ed5358e1..05df20ea7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate text_io; extern crate tokio; +use std::convert::TryFrom; use std::env; use std::path::Path; use std::str::FromStr; @@ -522,16 +523,16 @@ fn run() -> Result<(), failure::Error> { .takes_value(true) ) .arg( - Arg::with_name("local-https") + Arg::with_name("local-protocol") .help("Takes requests for local server via https instead of http") .long("local-https") - .takes_value(false) + .takes_value(true) ) .arg( - Arg::with_name("upstream-http") + Arg::with_name("upstream-protocol") .help("Sends requests to host via http instead of https") .long("upstream-http") - .takes_value(false) + .takes_value(true) ) .arg(verbose_arg.clone()) .arg(wrangler_file.clone()) @@ -776,8 +777,10 @@ fn run() -> Result<(), failure::Error> { let mut port: Option = matches .value_of("port") .map(|p| p.parse().expect("--port expects a number")); - let mut local_https: bool = matches.is_present("local-https"); - let mut upstream_http: bool = matches.is_present("upstream-http"); + + type Protocol = commands::dev::Protocol; + let mut local_protocol_str: Option<&str> = matches.value_of("local-protocol"); + let mut upstream_protocol_str: Option<&str> = matches.value_of("upstream-protocol"); // Check if arg not given but present in wrangler.toml if let Some(d) = &manifest.dev { @@ -789,12 +792,12 @@ fn run() -> Result<(), failure::Error> { port = d.port; } - if !local_https && d.local_https.is_some() { - local_https = d.local_https.unwrap(); + if local_protocol_str.is_none() && d.local_protocol.is_some() { + local_protocol_str = d.local_protocol.as_deref(); } - if !upstream_http && d.upstream_http.is_some() { - upstream_http = d.upstream_http.unwrap(); + if upstream_protocol_str.is_none() && d.upstream_protocol.is_some() { + upstream_protocol_str = d.upstream_protocol.as_deref(); } } @@ -805,15 +808,18 @@ fn run() -> Result<(), failure::Error> { let user = settings::global_user::GlobalUser::new().ok(); let verbose = matches.is_present("verbose"); - let server_config = commands::dev::ServerConfig::new(host, ip, port, upstream_http)?; + let local_protocol = Protocol::try_from(local_protocol_str.unwrap_or("http"))?; + let upstream_protocol = Protocol::try_from(upstream_protocol_str.unwrap_or("https"))?; + + let server_config = commands::dev::ServerConfig::new(host, ip, port, upstream_protocol)?; commands::dev::dev( target, deploy_config, user, server_config, - local_https, - upstream_http, + local_protocol, + upstream_protocol, verbose, )?; } else if matches.subcommand_matches("whoami").is_some() { diff --git a/src/settings/toml/dev.rs b/src/settings/toml/dev.rs index ffcef2342..1271b483b 100644 --- a/src/settings/toml/dev.rs +++ b/src/settings/toml/dev.rs @@ -5,6 +5,6 @@ use serde::{Deserialize, Serialize}; pub struct Dev { pub ip: Option, pub port: Option, - pub local_https: Option, - pub upstream_http: Option, + pub local_protocol: Option, + pub upstream_protocol: Option, } From 7100c06c72531f4411aac08766df56efc2ad876d Mon Sep 17 00:00:00 2001 From: jspspike Date: Wed, 12 Aug 2020 13:35:13 -0500 Subject: [PATCH 8/8] Changed http to protocol --- src/commands/dev/edge/server/mod.rs | 4 ++-- src/main.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/dev/edge/server/mod.rs b/src/commands/dev/edge/server/mod.rs index a9233a3a6..5a73eb2f6 100644 --- a/src/commands/dev/edge/server/mod.rs +++ b/src/commands/dev/edge/server/mod.rs @@ -17,7 +17,7 @@ fn preview_request( client: HyperClient>, preview_token: String, host: String, - http: Protocol, + protocol: Protocol, ) -> ResponseFuture { let (mut parts, body) = req.into_parts(); @@ -33,7 +33,7 @@ fn preview_request( HeaderValue::from_str(&preview_token).expect("Could not create token header"), ); - parts.uri = match http { + parts.uri = match protocol { Protocol::Http => format!("http://{}{}", host, path), Protocol::Https => format!("https://{}{}", host, path), } diff --git a/src/main.rs b/src/main.rs index 4e7b862d8..5c00ae1c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -524,14 +524,14 @@ fn run() -> Result<(), failure::Error> { ) .arg( Arg::with_name("local-protocol") - .help("Takes requests for local server via https instead of http") - .long("local-https") + .help("sets the protocol on which the wrangler dev listens, by default this is http but can be set to https") + .long("local-protocol") .takes_value(true) ) .arg( Arg::with_name("upstream-protocol") - .help("Sends requests to host via http instead of https") - .long("upstream-http") + .help("sets the protocol on which requests are sent to the host, by default this is https but can be set to http") + .long("upstream-protocol") .takes_value(true) ) .arg(verbose_arg.clone())