1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-06-03 09:04:12 +02:00

switch proxy to allow webscokets

This commit is contained in:
Robin Appelman 2023-02-15 21:25:03 +01:00
commit 6aad67473d
4 changed files with 111 additions and 208 deletions

134
Cargo.lock generated
View file

@ -511,6 +511,8 @@ dependencies = [
"enum_dispatch",
"flate2",
"futures-util",
"hyper",
"hyper-reverse-proxy",
"maplit",
"miette",
"opener",
@ -527,7 +529,6 @@ dependencies = [
"tracing",
"tracing-subscriber",
"warp",
"warp-reverse-proxy",
]
[[package]]
@ -603,9 +604,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
@ -615,9 +616,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.20"
version = "0.14.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
dependencies = [
"bytes",
"futures-channel",
@ -638,16 +639,14 @@ dependencies = [
]
[[package]]
name = "hyper-rustls"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
name = "hyper-reverse-proxy"
version = "0.5.2-dev"
source = "git+https://github.com/felipenoris/hyper-reverse-proxy#e73a76600ce9e51e962de5266b03be596e6c1d50"
dependencies = [
"http",
"hyper",
"rustls",
"lazy_static",
"tokio",
"tokio-rustls",
"tracing",
]
[[package]]
@ -1148,7 +1147,6 @@ dependencies = [
"http",
"http-body",
"hyper",
"hyper-rustls",
"ipnet",
"js-sys",
"lazy_static",
@ -1156,64 +1154,24 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[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",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[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.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
dependencies = [
"base64",
]
[[package]]
name = "ryu"
version = "1.0.10"
@ -1232,16 +1190,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[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.142"
@ -1378,20 +1326,14 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "socket2"
version = "0.4.4"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.10.0"
@ -1626,17 +1568,6 @@ 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.9"
@ -1854,12 +1785,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.2.2"
@ -1930,20 +1855,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "warp-reverse-proxy"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bb86f4c86d0fb386acf34fa5360ea0e8a082835e05296da2286c38a3316283"
dependencies = [
"hyper",
"once_cell",
"reqwest",
"thiserror",
"unicase",
"warp",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -2026,25 +1937,6 @@ dependencies = [
"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 = "webpki-roots"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -28,10 +28,11 @@ enum_dispatch = "0.3.8"
miette = { version = "5.2.0", features = ["fancy"] }
shell-words = "1.1.0"
warp = "0.3"
warp-reverse-proxy = { version = "0.5.0", default_features = false, features = ["rustls-tls"] }
tokio-stream = { version = "0.1.9", features = ["net"] }
tracing = "0.1.35"
tracing-subscriber = "0.3.14"
hyper-reverse-proxy = { version = "0.5.2-dev", git = "https://github.com/felipenoris/hyper-reverse-proxy" }
hyper = "0.14.24"
[profile.release]
lto = true

View file

@ -37,12 +37,13 @@ haze start [database] [php-version]
```
Where `database` is one of `sqlite`, `mysql`, `mariadb` or `pgsql` with an optional version (e.g. `pgsql:12`), defaults to `sqlite`.
And `php-version` is one of `7.3`, `7.4`, `8.0`, `7` or `8`, defaults to `8.0`
And `php-version` is one of `7.3`, `7.4`, `8.0`, `8.1`, `7` or `8`, defaults to `8.1`
Additionally, you can use the following options when starting an instance:
- `s3`: setup an S3 server and configure to Nextcloud to use it as primary storage
- `<path to app.tar.gz>`: by specifying the path to an app package this package will be extracted into the apps directory of the new instance (overwriting any existing app code). This can be used to quickly test a packaged app.
- `ldap`: setup an LDAP server
- `office`: setup a Nextcloud Office server
- `onlyoffice` setup an onlyoffice document server
- `push` setup [client push](https://github.com/nextcloud/notify_push)
- `smb`: setup a samba server for external storage use

View file

@ -2,31 +2,29 @@ use crate::service::ServiceTrait;
use crate::{Cloud, HazeConfig};
use crate::{Result, Service};
use bollard::Docker;
use futures_util::future::Either;
use futures_util::FutureExt;
use miette::{miette, Context, IntoDiagnostic};
use hyper::http::uri::Builder;
use miette::{miette, IntoDiagnostic};
use std::collections::HashMap;
use std::convert::Infallible;
use std::fs::{create_dir_all, remove_file, set_permissions};
use std::net::SocketAddr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::net::UnixListener;
use tokio::net::{UnixListener, UnixStream};
use tokio::signal::ctrl_c;
use tokio::spawn;
use tokio::time::sleep;
use tokio_stream::wrappers::UnixListenerStream;
use tracing::info;
use warp::host::Authority;
use warp::http::{HeaderMap, HeaderValue, Method};
use warp::hyper::body::Bytes;
use warp::path::FullPath;
use warp::Filter;
use warp_reverse_proxy::{
extract_request_data_filter, proxy_to_and_forward_response, QueryParameters,
};
use warp::http::header::HOST;
use warp::http::{HeaderValue, Uri};
use warp::hyper::server::accept::from_stream;
use warp::hyper::server::conn::AddrStream;
use warp::hyper::service::{make_service_fn, service_fn};
use warp::hyper::{Body, Request, Response, Server, StatusCode};
struct ActiveInstances {
known: Mutex<HashMap<String, SocketAddr>>,
@ -126,8 +124,6 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
let instances = Arc::new(instances);
let base_address = Arc::new(base_address);
let last_instances = instances.clone();
let instances = warp::any().map(move || instances.clone());
let base_address = warp::any().map(move || base_address.clone());
spawn(async move {
loop {
@ -136,73 +132,31 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
}
});
let proxy = warp::any()
.and(warp::filters::host::optional())
.and(instances)
.and(base_address)
.and_then(
move |host: Option<Authority>,
instances: Arc<ActiveInstances>,
base_address: Arc<String>| async move {
let host = match host {
Some(host) => host,
None => return Err(warp::reject::not_found()),
};
let ip = if host.as_str() == base_address.as_str() {
instances
.last()
.ok_or_else(|| String::from("No running instance known"))
} else {
let requested_instance = host.as_str().split('.').next().unwrap();
instances
.get(requested_instance)
.await
.ok_or_else(|| format!("Error {} has no known ip", requested_instance))
};
match ip {
Ok(ip) => Ok((format!("http://{}", ip), host.to_string())),
Err(e) => {
eprintln!("{}", e);
Err(warp::reject::not_found())
}
}
},
)
.untuple_one()
.and(extract_request_data_filter())
.and_then(
move |proxy_address: String,
host: String,
uri: FullPath,
params: QueryParameters,
method: Method,
mut headers: HeaderMap,
body: Bytes| {
headers.insert("host", HeaderValue::from_str(&host).unwrap());
proxy_to_and_forward_response(
proxy_address,
String::new(),
uri,
params,
method,
headers,
body,
)
},
);
let cancel = async {
ctrl_c().await.ok();
};
let warp_server = warp::serve(proxy);
let handler = move |remote_addr| {
let instances = instances.clone();
let base_address = base_address.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
handle(remote_addr, req, instances.clone(), base_address.clone())
}))
}
};
let server = if !listen.starts_with('/') {
let listen = SocketAddr::from_str(&listen)
.into_diagnostic()
.wrap_err("Failed to parse proxy listen address")?;
Either::Left(warp_server.bind_with_graceful_shutdown(listen, cancel).1)
if !listen.starts_with('/') {
let make_svc = make_service_fn(|conn: &AddrStream| handler(conn.remote_addr().ip()));
let addr: SocketAddr = listen.parse().into_diagnostic()?;
Server::bind(&addr)
.serve(make_svc)
.with_graceful_shutdown(cancel)
.await
.into_diagnostic()?;
} else {
let make_svc =
make_service_fn(move |_conn: &UnixStream| handler(Ipv4Addr::UNSPECIFIED.into()));
let listen: PathBuf = listen.into();
if let Some(parent) = listen.parent() {
if !parent.exists() {
@ -215,15 +169,70 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
let listener = UnixListener::bind(&listen).into_diagnostic()?;
set_permissions(&listen, PermissionsExt::from_mode(0o666)).into_diagnostic()?;
let stream = UnixListenerStream::new(listener);
Either::Right(
warp_server
.serve_incoming_with_graceful_shutdown(stream, cancel)
.map(move |_| {
remove_file(&listen).ok();
}),
)
};
let acceptor = from_stream(stream);
Server::builder(acceptor)
.serve(make_svc)
.with_graceful_shutdown(cancel)
.await
.into_diagnostic()?;
}
server.await;
Ok(())
}
async fn get_remote(
host: Option<&HeaderValue>,
instances: &ActiveInstances,
base_address: &str,
) -> Result<SocketAddr, String> {
let host = match host.and_then(|host| host.to_str().ok()) {
Some(host) => host,
None => return Err("No or invalid hostname provided".into()),
};
let ip = if host == base_address {
instances
.last()
.ok_or_else(|| String::from("No running instance known"))
} else {
let requested_instance = host.split('.').next().unwrap();
instances
.get(requested_instance)
.await
.ok_or_else(|| format!("Error {} has no known ip", requested_instance))
};
match ip {
Ok(ip) => Ok(ip),
Err(e) => {
eprintln!("{}", e);
Err(e)
}
}
}
async fn handle(
client_ip: IpAddr,
req: Request<Body>,
instances: Arc<ActiveInstances>,
base_address: Arc<String>,
) -> Result<Response<Body>, Infallible> {
let host = req.headers().get(HOST);
let remote = match get_remote(host, &instances, &base_address).await {
Ok(remote) => remote,
Err(e) => {
return Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(e.into())
.unwrap())
}
};
let forward = format!("http://{}", remote);
let client = hyper::Client::builder().build_http();
match hyper_reverse_proxy::call(client_ip, &forward, req, &client).await {
Ok(response) => Ok(response),
Err(_error) => Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::empty())
.unwrap()),
}
}