diff --git a/Cargo.lock b/Cargo.lock index 8c672a9..12ef3eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,6 +95,45 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "async-compression" version = "0.4.18" @@ -797,6 +836,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1675,7 +1728,7 @@ dependencies = [ "oco_ref", "or_poisoned", "paste", - "rand", + "rand 0.8.5", "reactive_graph", "rustc-hash 2.1.0", "send_wrapper", @@ -2107,6 +2160,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -2119,7 +2182,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2189,6 +2252,15 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -2247,7 +2319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2383,7 +2455,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2513,8 +2585,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.14", ] [[package]] @@ -2524,7 +2607,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -2536,6 +2629,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.14", +] + [[package]] name = "rcgen" version = "0.13.2" @@ -2546,6 +2649,7 @@ dependencies = [ "ring", "rustls-pki-types", "time", + "x509-parser", "yasna", ] @@ -2690,7 +2794,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -2740,6 +2844,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" @@ -2760,6 +2873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "aws-lc-rs", + "log", "once_cell", "rustls-pki-types", "rustls-webpki", @@ -3017,7 +3131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3079,6 +3193,7 @@ dependencies = [ "axum", "axum-server", "futures", + "rustls", "serde", "serde_json", "sqlx", @@ -3114,10 +3229,13 @@ dependencies = [ "leptos_meta", "leptos_router", "pbkdf2", + "rand 0.9.0", "rcgen", "rpassword", + "rustls-pki-types", "serde", "sha2", + "sparse-actions", "sparse-handler", "sqlx", "structopt", @@ -3287,7 +3405,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -3326,7 +3444,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -3934,7 +4052,7 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", "thiserror 1.0.69", "utf-8", @@ -4465,6 +4583,24 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -4517,7 +4653,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -4531,6 +4676,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 6fe9ff3..e6c31ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ lto = true codegen-units = 1 panic = "abort" +[profile.dev] +panic = "abort" + [profile.wasm-release] inherits = "release" strip = true diff --git a/flake.nix b/flake.nix index c4162ae..e97045f 100644 --- a/flake.nix +++ b/flake.nix @@ -99,6 +99,7 @@ craneLib = (crane.mkLib pkgs).overrideToolchain (p: p.rust-bin.nightly.latest.default.override { + extensions = [ "rust-src" ]; targets = [ "x86_64-unknown-linux-gnu" "x86_64-unknown-linux-musl" @@ -138,9 +139,10 @@ set -eux ${setup-zig-freebsd}/bin/setup-zig-freebsd - cargo build -p sparse-windows-beacon --target=x86_64-pc-windows-gnu - cargo build -p sparse-unix-beacon --target=x86_64-unknown-freebsd - cargo build -p sparse-unix-beacon --target=x86_64-unknown-linux-musl + + mkdir -p target/x86_64-{unknown-{linux-musl,freebsd},pc-windows-gnu}/debug + touch target/x86_64-unknown-{linux-musl,freebsd}/debug/sparse-unix-{beacon,installer} + touch target/x86_64-pc-windows-gnu/debug/sparse-windows-{beacon.dll,installer.exe} ''; in { packages = outputs.packages; @@ -155,15 +157,18 @@ # Added to make development easier SPARSE_INSTALLER_LINUX = - "../../target/x86_64-unknown-linux-musl/debug/sparse-unix-beacon"; + "../../target/x86_64-unknown-linux-musl/debug/sparse-unix-installer"; SPARSE_INSTALLER_FREEBSD = - "../../target/x86_64-unknown-freebsd/debug/sparse-unix-beacon"; + "../../target/x86_64-unknown-freebsd/debug/sparse-unix-installer"; + SPARSE_INSTALLER_WINDOWS = + "../../target/x86_64-pc-windows-gnu/debug/sparse-windows-installer.exe"; + SPARSE_BEACON_LINUX = "../../target/x86_64-unknown-linux-musl/debug/sparse-unix-beacon"; SPARSE_BEACON_FREEBSD = "../../target/x86_64-unknown-freebsd/debug/sparse-unix-beacon"; SPARSE_BEACON_WINDOWS = - "../../target/x86_64-pc-windows-gnu/debug/sparse-windows-beacon.exe"; + "../../../target/x86_64-pc-windows-gnu/debug/sparse-windows-beacon.dll"; shellHook = '' export DATABASE_URL="sqlite://$(${pkgs.git}/bin/git rev-parse --show-toplevel)/sparse-server/db.sqlite" diff --git a/packages.nix b/packages.nix index a7c4086..0651035 100644 --- a/packages.nix +++ b/packages.nix @@ -288,8 +288,9 @@ let outputs = rec { packages = { - inherit sparse-server sparse-installer-linux sparse-installer-freebsd - sparse-installer-windows; + inherit sparse-server sparse-beacon-linux sparse-beacon-freebsd + sparse-beacon-windows linux-loader freebsd-loader sparse-installer-linux + sparse-installer-freebsd sparse-installer-windows; inherit freebsd-zig-libc; diff --git a/sparse-handler/Cargo.toml b/sparse-handler/Cargo.toml index 060f114..0cc45f6 100644 --- a/sparse-handler/Cargo.toml +++ b/sparse-handler/Cargo.toml @@ -14,3 +14,4 @@ sqlx = { version = "0.8", default-features = false, features = ["chrono", "macro serde = "1.0" serde_json = "1.0" axum-server = { version = "^0.7", features = ["tokio-rustls", "tls-rustls"] } +rustls = "0.23" diff --git a/sparse-handler/src/error.rs b/sparse-handler/src/error.rs index 42d1c6e..cedbf27 100644 --- a/sparse-handler/src/error.rs +++ b/sparse-handler/src/error.rs @@ -4,6 +4,7 @@ pub enum Error { Sqlx(sqlx::Error), TokioJoin(tokio::task::JoinError), Io(std::io::Error), + Rustls(rustls::Error), } impl std::fmt::Display for Error { @@ -21,6 +22,9 @@ impl std::fmt::Display for Error { Error::Io(err) => { write!(f, "io error: {err:?}") } + Error::Rustls(err) => { + write!(f, "rustls error: {err:?}") + } } } } @@ -31,6 +35,7 @@ impl std::error::Error for Error { Error::Sqlx(err) => Some(err), Error::TokioJoin(err) => Some(err), Error::Io(err) => Some(err), + Error::Rustls(err) => Some(err), _ => None, } } @@ -61,3 +66,9 @@ impl From for Error { Self::Io(err) } } + +impl From for Error { + fn from(err: rustls::Error) -> Self { + Self::Rustls(err) + } +} diff --git a/sparse-handler/src/lib.rs b/sparse-handler/src/lib.rs index df18bea..54182e7 100644 --- a/sparse-handler/src/lib.rs +++ b/sparse-handler/src/lib.rs @@ -4,7 +4,6 @@ use std::{ }; use axum::routing::{get, post, Router}; -use axum_server::tls_rustls::RustlsConfig; use sqlx::SqlitePool; use tokio::task::JoinHandle; @@ -36,8 +35,6 @@ impl std::ops::Deref for BeaconListenerMap { } pub async fn start_all_listeners(beacon_listener_map: BeaconListenerMap, db: SqlitePool) -> Result<(), crate::error::Error> { - tracing::debug!("Typeid: {:?}", std::any::TypeId::of::()); - let listener_ids = sqlx::query!("SELECT listener_id FROM beacon_listener") .fetch_all(&db) .await?; @@ -56,6 +53,15 @@ struct ListenerState { db: SqlitePool } +struct Listener { + listener_id: i64, + port: i64, + public_ip: String, + domain_name: String, + certificate: Vec, + privkey: Vec +} + pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id: i64, db: SqlitePool) -> Result<(), crate::error::Error> { { let Ok(blm_handle) = beacon_listener_map.read() else { @@ -66,7 +72,7 @@ pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id: return Err(crate::error::Error::Generic("Beacon listener already started".to_string())); } } - let listener = sqlx::query!("SELECT * FROM beacon_listener WHERE listener_id = ?", listener_id) + let listener = sqlx::query_as!(Listener, "SELECT * FROM beacon_listener WHERE listener_id = ?", listener_id) .fetch_one(&db) .await?; @@ -84,17 +90,30 @@ pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id: let hidden_app = Router::new().nest("/hidden_sparse", app); - let tls_config = RustlsConfig::from_pem( - listener.certificate.as_bytes().to_vec(), - listener.privkey.as_bytes().to_vec() - ).await?; + let keypair = match rustls::pki_types::PrivateKeyDer::try_from(listener.privkey.clone()) { + Ok(pk) => pk, + Err(e) => { + return Err(crate::error::Error::Generic(format!("Could not parse private key: {e}"))); + } + }; + let cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone()); + + let mut tls_config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(vec![cert], keypair)?; + tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let addr = std::net::SocketAddr::from(([0, 0, 0, 0], listener.port as u16)); tracing::debug!("Starting listener {}, {}, on port {}", listener_id, listener.domain_name, listener.port); let join_handle = tokio::task::spawn(async move { - let res = axum_server::tls_rustls::bind_rustls(addr, tls_config) + let res = axum_server::tls_rustls::bind_rustls( + addr, + axum_server::tls_rustls::RustlsConfig::from_config( + Arc::new(tls_config) + ) + ) .serve(hidden_app.into_make_service()) .await; diff --git a/sparse-server/Cargo.toml b/sparse-server/Cargo.toml index d05155b..d39a79d 100644 --- a/sparse-server/Cargo.toml +++ b/sparse-server/Cargo.toml @@ -9,12 +9,12 @@ crate-type = ["cdylib", "rlib"] [dependencies] leptos = { version = "^0.7", features = ["nightly"] } leptos_router = { version = "^0.7", features = ["nightly"] } -axum = { version = "^0.7", features = ["ws", "macros"], optional = true } +axum = { version = "^0.7", features = ["ws", "macros", "http2"], optional = true } axum-extra = { version = "^0.9", features = ["cookie"], optional = true } console_error_panic_hook = "0.1" leptos_axum = { version = "^0.7", optional = true } leptos_meta = { version = "^0.7" } -tokio = { version = "1", features = ["rt-multi-thread", "signal"], optional = true } +tokio = { version = "1", features = ["rt-multi-thread", "signal", "fs", "io-std", "io-util"], optional = true } tower = { version = "0.4", optional = true } tower-http = { version = "0.5", features = ["fs", "compression-br", "compression-deflate", "compression-gzip", "compression-zstd", "trace"], optional = true } wasm-bindgen = "0.2" @@ -39,9 +39,12 @@ sha2 = { version = "0.10", optional = true } hex = { version = "0.4", optional = true } serde = "1.0" cfg-if = "1.0.0" -rcgen = { version = "0.13.2", optional = true } +rcgen = { version = "0.13.2", features = ["pem", "x509-parser", "crypto"], optional = true } cron = { version = "0.15.0", optional = true } +rustls-pki-types = { version = "1.7", optional = true } +rand = { version = "0.9", optional = true } +sparse-actions = { path = "../sparse-actions", optional = true } sparse-handler = { path = "../sparse-handler", optional = true } [features] @@ -67,6 +70,9 @@ ssr = [ "dep:rcgen", "dep:cron", "dep:sparse-handler", + "dep:rustls-pki-types", + "dep:sparse-actions", + "dep:rand", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", diff --git a/sparse-server/migrations/20250202045114_mtls.sql b/sparse-server/migrations/20250202045114_mtls.sql new file mode 100644 index 0000000..27d43cc --- /dev/null +++ b/sparse-server/migrations/20250202045114_mtls.sql @@ -0,0 +1,14 @@ +-- Add migration script here +ALTER TABLE beacon_template ADD COLUMN client_key blob NOT NULL DEFAULT ''; +ALTER TABLE beacon_template ADD COLUMN client_cert blob NOT NULL DEFAULT ''; + +DROP TABLE beacon_listener; +CREATE TABLE beacon_listener ( + listener_id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + + port int NOT NULL, + public_ip varchar NOT NULL, + domain_name varchar NOT NULL, + certificate blob NOT NULL, + privkey blob NOT NULL +); diff --git a/sparse-server/src/beacons.rs b/sparse-server/src/beacons.rs index d364187..b49cfc9 100644 --- a/sparse-server/src/beacons.rs +++ b/sparse-server/src/beacons.rs @@ -112,6 +112,7 @@ pub fn provide_beacon_resources() { #[component] pub fn BeaconView() -> impl IntoView { + #[cfg(feature = "hydrate")] Effect::new(move || { let user = expect_context::>>(); if user.get().is_none() { diff --git a/sparse-server/src/beacons/categories.rs b/sparse-server/src/beacons/categories.rs index d6bc60e..5472dee 100644 --- a/sparse-server/src/beacons/categories.rs +++ b/sparse-server/src/beacons/categories.rs @@ -101,7 +101,6 @@ pub async fn rename_category(id: i64, name: String) -> Result<(), ServerFnError> pub fn CategoriesView() -> impl IntoView { let BeaconResources { add_category, categories, .. } = expect_context(); - view! {

"Categories"

diff --git a/sparse-server/src/beacons/listeners.rs b/sparse-server/src/beacons/listeners.rs index 2285516..8e1a433 100644 --- a/sparse-server/src/beacons/listeners.rs +++ b/sparse-server/src/beacons/listeners.rs @@ -71,6 +71,17 @@ pub async fn get_listeners() -> Result, ServerFnError> { .collect()) } +#[cfg(feature = "ssr")] +pub fn generate_cert_from_keypair(kp: &rcgen::KeyPair, names: Vec) -> Result { + use rcgen::CertificateParams; + + let mut params = CertificateParams::new(names)?; + + params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + + params.self_signed(&kp) +} + #[server] pub async fn add_listener(public_ip: String, port: i16, domain_name: String) -> Result<(), ServerFnError> { let user = user::get_auth_session().await?; @@ -84,15 +95,19 @@ pub async fn add_listener(public_ip: String, port: i16, domain_name: String) -> } let subject_alt_names = vec![public_ip.to_string(), domain_name.clone()]; - let CertifiedKey { cert, key_pair } = tokio::task::spawn_blocking(|| { - generate_simple_self_signed(subject_alt_names) + + + let (key_pair, cert) = tokio::task::spawn_blocking(|| { + rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256) + .and_then(|keypair| + generate_cert_from_keypair(&keypair, subject_alt_names).map(|cert| (keypair, cert))) }).await??; let db = expect_context::(); let public_ip = public_ip.to_string(); - let cert = cert.pem().to_string(); - let key_pair = key_pair.serialize_pem().to_string(); + let cert = cert.der().to_vec(); + let key_pair = key_pair.serialize_der(); sqlx::query!( "INSERT INTO beacon_listener (port, public_ip, domain_name, certificate, privkey) VALUES (?, ?, ?, ?, ?)", @@ -116,23 +131,32 @@ pub async fn remove_listener(listener_id: i64) -> Result<(), ServerFnError> { return Err(ServerFnError::::ServerError("You are not signed in!".to_owned())); } - let pool = expect_context::(); - let blm = expect_context::(); + { + let blm = expect_context::(); - let Ok(mut blm_handle) = blm.write() else { - return Err(ServerFnError::::ServerError("Failed to get write handle for beacon listener map".to_owned())); - }; + let Ok(mut blm_handle) = blm.write() else { + return Err(ServerFnError::::ServerError("Failed to get write handle for beacon listener map".to_owned())); + }; - if let Some(mut bl) = blm_handle.get_mut(&listener_id) { - bl.abort(); - } else { - return Err(ServerFnError::::ServerError("Failed to get write handle for beacon listener map".to_owned())); + if let Some(bl) = blm_handle.get_mut(&listener_id) { + bl.abort(); + } else { + return Err(ServerFnError::::ServerError("Failed to get write handle for beacon listener map".to_owned())); + } + + blm_handle.remove(&listener_id); } - blm_handle.remove(&listener_id); - drop(blm_handle); + let pool = expect_context::(); - unimplemented!() + sqlx::query!( + "DELETE FROM beacon_listener WHERE listener_id = ?", + listener_id + ) + .execute(&pool) + .await?; + + Ok(()) } #[server] diff --git a/sparse-server/src/beacons/templates.rs b/sparse-server/src/beacons/templates.rs index 23597da..001b7e9 100644 --- a/sparse-server/src/beacons/templates.rs +++ b/sparse-server/src/beacons/templates.rs @@ -103,6 +103,34 @@ pub async fn add_template( let db = expect_context::(); + let listener = sqlx::query!( + "SELECT certificate, privkey FROM beacon_listener WHERE listener_id = ?", + listener_id + ) + .fetch_one(&db) + .await?; + + use rcgen::{Certificate, CertificateParams, KeyPair}; + + let keypair = KeyPair::from_der_and_sign_algo( + match &rustls_pki_types::PrivateKeyDer::try_from(&*listener.privkey) { + Ok(pk) => pk, + Err(e) => { + srverr!("Could not parse private key: {e}"); + } + }, + &rcgen::PKCS_ECDSA_P256_SHA256 + )?; + let ca_params = CertificateParams::from_ca_cert_der(&(*listener.certificate).into())?; + let ca_cert = ca_params.self_signed(&keypair)?; + + let client_key = KeyPair::generate()?; + let client_params = CertificateParams::default(); + let client_cert = client_params.signed_by(&client_key, &ca_cert, &keypair)?; + + let client_key_der = client_key.serialize_der(); + let client_cert_der = client_cert.der().to_vec(); + match &*source_mode { "host" => { let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00"); @@ -110,16 +138,18 @@ pub async fn add_template( sqlx::query!( r"INSERT INTO beacon_template - (template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, default_category) + (template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, default_category, client_key, client_cert) VALUES - (?, ?, ?, ?, ?, ?, 'host', ?)", + (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?)", template_name, operating_system, config_id, listener_id, source_ip, source_mac, - default_category + default_category, + client_key_der, + client_cert_der ) .execute(&db) .await?; @@ -132,9 +162,9 @@ pub async fn add_template( sqlx::query!( r"INSERT INTO beacon_template - (template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, source_netmask, source_gateway, default_category) + (template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, source_netmask, source_gateway, default_category, client_key, client_cert) VALUES - (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?)", + (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?)", template_name, operating_system, config_id, @@ -143,7 +173,9 @@ pub async fn add_template( source_mac, source_netmask, source_gateway, - default_category + default_category, + client_key_der, + client_cert_der ) .execute(&db) .await?; @@ -376,9 +408,13 @@ pub fn DisplayTemplates( ")"
- + diff --git a/sparse-server/src/error.rs b/sparse-server/src/error.rs index 7df4581..f4d4de7 100644 --- a/sparse-server/src/error.rs +++ b/sparse-server/src/error.rs @@ -10,6 +10,7 @@ pub enum Error { Pbkdf2(pbkdf2::password_hash::errors::Error), #[cfg(feature = "ssr")] Io(std::io::Error), + AddrParse(std::net::AddrParseError), } impl std::fmt::Display for Error { @@ -37,6 +38,9 @@ impl std::fmt::Display for Error { Error::Io(err) => { write!(f, "io error: {err:?}") } + Error::AddrParse(err) => { + write!(f, "ip address parse error: {err:?}") + } } } } @@ -50,11 +54,23 @@ impl std::error::Error for Error { Error::TokioJoin(err) => Some(err), #[cfg(feature = "ssr")] Error::Io(err) => Some(err), + Error::AddrParse(err) => Some(err), _ => None, } } } +#[cfg(feature = "ssr")] +impl axum::response::IntoResponse for Error { + fn into_response(self) -> axum::response::Response { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + format!("{:?}", self), + ) + .into_response() + } +} + impl std::str::FromStr for Error { type Err = Self; @@ -90,3 +106,9 @@ impl From for Error { Self::Io(err) } } + +impl From for Error { + fn from(err: std::net::AddrParseError) -> Self { + Self::AddrParse(err) + } +} diff --git a/sparse-server/src/main.rs b/sparse-server/src/main.rs index 7dcfe50..bfe3f2d 100644 --- a/sparse-server/src/main.rs +++ b/sparse-server/src/main.rs @@ -1,19 +1,3 @@ -#[cfg(feature = "hydrate")] -pub(crate) mod beacon_binaries { - #[allow(dead_code)] - pub const LINUX_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX")); - #[allow(dead_code)] - pub const FREEBSD_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_FREEBSD")); - #[allow(dead_code)] - pub const WINDOWS_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_WINDOWS")); - - #[allow(dead_code)] - pub const LINUX_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_LINUX")); - #[allow(dead_code)] - pub const FREEBSD_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD")); - #[allow(dead_code)] - pub const WINDOWS_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS")); -} #[cfg(feature = "ssr")] mod cli; @@ -29,6 +13,7 @@ pub mod db; #[cfg(feature = "ssr")] #[tokio::main] async fn main() -> anyhow::Result { + use std::{path::PathBuf, process::ExitCode, str::FromStr}; use structopt::StructOpt; @@ -38,7 +23,7 @@ async fn main() -> anyhow::Result { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| format!("{}=debug,sparse_handler=debug,tower_http=trace", env!("CARGO_CRATE_NAME")).into()), + .unwrap_or_else(|_| format!("{}=debug,sparse_handler=debug", env!("CARGO_CRATE_NAME")).into()), ) .with(tracing_subscriber::fmt::layer()) .init(); diff --git a/sparse-server/src/webserver.rs b/sparse-server/src/webserver.rs index 278a971..9ce9447 100644 --- a/sparse-server/src/webserver.rs +++ b/sparse-server/src/webserver.rs @@ -1,13 +1,167 @@ use std::{net::SocketAddrV4, process::ExitCode}; use sqlx::sqlite::SqlitePool; -use axum::Router; +use axum::{extract::{FromRef, Path, State}, response::IntoResponse, Router, routing::get}; use leptos::prelude::*; use leptos_axum::{generate_route_list, LeptosRoutes}; use tokio::signal; use sparse_server::app::*; +#[cfg(not(debug_assertions))] +pub(crate) mod beacon_binaries { + pub const LINUX_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_LINUX")); + pub const FREEBSD_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD")); + pub const WINDOWS_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS")); +} + +#[cfg(debug_assertions)] +pub async fn get_installer(btype: &str) -> Result, crate::error::Error> { + let path = match btype { + "linux" => "target/x86_64-unknown-linux-musl/debug/sparse-unix-installer", + "freebsd" => "target/x86_64-unknown-freebsd/debug/sparse-unix-installer", + "windows" => "target/x86_64-pc-windows-gnu/debug/sparse-unix-installer", + other => return Err(crate::error::Error::Generic(format!("unknown beacon type: {other}"))), + }; + + Ok(tokio::fs::read(path).await?) +} +#[cfg(not(debug_assertions))] +pub async fn get_installer(btype: &str) -> Result, crate::error::Error> { + match btype { + "linux" => Ok(beacon_binaries::LINUX_INSTALLER.to_owned()), + "windows" => Ok(beacon_binaries::WINDOWS_INSTALLER.to_owned()), + "freebsd" => Ok(beacon_binaries::FREEBSD_INSTALLER.to_owned()), + other => Err(crate::error::Error::Generic(format!("unknown beacon type: {other}"))) + } +} + +#[derive(FromRef, Clone, Debug)] +pub struct AppState { + db: SqlitePool, + leptos_options: leptos::config::LeptosOptions +} + +#[axum::debug_handler] +pub async fn download_beacon_installer( + Path(template_id): Path, + State(db): State +) -> Result { + use rand::{rngs::OsRng, TryRngCore}; + use sparse_actions::payload_types::Parameters_t; + + let mut parameters_buffer = vec![0u8; std::mem::size_of::()]; + //let _ = OsRng.try_fill_bytes(&mut parameters_buffer); + + let parameters: &mut Parameters_t = unsafe { std::mem::transmute(parameters_buffer.as_mut_ptr()) }; + + let template = sqlx::query!( + r"SELECT operating_system, source_ip, source_mac, source_mode, source_netmask, + source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key + FROM beacon_template JOIN beacon_listener" + ) + .fetch_one(&db.db) + .await?; + + let dest_ip = template.public_ip.parse::()?; + let src_ip = template.source_ip.parse::()?; + + let dest_octets = dest_ip.octets(); + parameters.destination_ip.a = dest_octets[0]; + parameters.destination_ip.b = dest_octets[1]; + parameters.destination_ip.c = dest_octets[2]; + parameters.destination_ip.d = dest_octets[3]; + + let src_mac: [u8; 6] = template + .source_mac + .unwrap_or("00:00:00:00:00:00".to_string()) + .split(":") + .map(|by| u8::from_str_radix(by, 16)) + .collect::, _>>() + .map_err(|_| crate::error::Error::Generic("Could not parse source MAC address".to_string())) + .and_then( + |bytes| bytes.try_into().map_err(|_| crate::error::Error::Generic("Could not parse source MAC address".to_string())) + )?; + + let src_octets = src_ip.octets(); + match (template.source_mode.as_deref(), template.source_netmask, template.source_gateway) { + (Some("custom"), Some(nm), Some(ip)) => unsafe { + let gateway = ip.parse::()?; + let gw_octets = gateway.octets(); + + parameters.source_ip.custom_networking.mode = 0; + parameters.source_ip.custom_networking.source_mac.copy_from_slice(&src_mac[..]); + parameters.source_ip.custom_networking.netmask = nm as u16; + parameters.source_ip.custom_networking.source_ip.a = src_octets[0]; + parameters.source_ip.custom_networking.source_ip.b = src_octets[1]; + parameters.source_ip.custom_networking.source_ip.c = src_octets[2]; + parameters.source_ip.custom_networking.source_ip.d = src_octets[3]; + parameters.source_ip.custom_networking.gateway.a = gw_octets[0]; + parameters.source_ip.custom_networking.gateway.b = gw_octets[1]; + parameters.source_ip.custom_networking.gateway.c = gw_octets[2]; + parameters.source_ip.custom_networking.gateway.d = gw_octets[3]; + } + (Some("host"), _, _) => unsafe { + parameters.source_ip.use_host_networking.mode = 1; + parameters.source_ip.use_host_networking.source_mac.copy_from_slice(&src_mac[..]); + parameters.source_ip.use_host_networking.source_ip.a = src_octets[0]; + parameters.source_ip.use_host_networking.source_ip.b = src_octets[1]; + parameters.source_ip.use_host_networking.source_ip.c = src_octets[2]; + parameters.source_ip.use_host_networking.source_ip.d = src_octets[3]; + } + _ => { + return Err(crate::error::Error::Generic("Could not parse host networking configuration".to_string())); + } + } + + parameters.destination_port = template.port as u16; + parameters.template_id = template_id as u16; + + let pubkey_cert = template.certificate; + parameters.pubkey_cert[..pubkey_cert.len()].copy_from_slice(&pubkey_cert[..]); + parameters.pubkey_cert_size = pubkey_cert.len() as u16; + + let client_key = template.client_key; + parameters.client_key[..client_key.len()].copy_from_slice(&client_key[..]); + parameters.client_key_length = client_key.len() as u16; + + let client_cert = template.client_cert; + parameters.client_cert[..client_cert.len()].copy_from_slice(&client_cert[..]); + parameters.client_cert_length = client_cert.len() as u16; + + let domain_name = template.domain_name.as_bytes(); + parameters.domain_name[..domain_name.len()].copy_from_slice(&domain_name[..]); + parameters.domain_name_length = domain_name.len() as u16; + + let installer_bytes = get_installer(&template.operating_system).await?; + + use axum::http::header; + + Ok(( + [ + ( + header::CONTENT_TYPE, + "application/octet-stream".to_string() + ), + ( + header::CONTENT_DISPOSITION, + format!( + r#"attachement; filename="sparse-installer{}""#, + if template.operating_system == "windows" { + ".exe" + } else { + "" + } + ) + ) + ], + [ + &installer_bytes[..], + ¶meters_buffer[..] + ].concat() + )) +} + pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyhow::Result { let conf = get_configuration(None).unwrap(); let leptos_options = conf.leptos_options; @@ -22,9 +176,15 @@ pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyh sparse_handler::start_all_listeners(beacon_listeners.clone(), db.clone()).await?; + let state = AppState { + leptos_options: leptos_options.clone(), + db: db.clone() + }; + let app = Router::new() + .route("/installer/:template_id", get(download_beacon_installer)) .leptos_routes_with_context( - &leptos_options, + &state, routes, move || { provide_context(beacon_listeners.clone()); @@ -36,7 +196,7 @@ pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyh } ) .fallback(leptos_axum::file_and_error_handler::(shell)) - .with_state(leptos_options) + .with_state(state) .layer( tower::ServiceBuilder::new() .layer(tower_http::trace::TraceLayer::new_for_http()) diff --git a/sparse-server/style/beacons/_categories.scss b/sparse-server/style/beacons/_categories.scss index 1e3334e..76b6f57 100644 --- a/sparse-server/style/beacons/_categories.scss +++ b/sparse-server/style/beacons/_categories.scss @@ -1,5 +1,6 @@ main.beacons div.categories { padding: 10px; + overflow-y: scroll; fieldset { display: grid; diff --git a/sparse-server/style/beacons/_configs.scss b/sparse-server/style/beacons/_configs.scss index bc76898..8d9febd 100644 --- a/sparse-server/style/beacons/_configs.scss +++ b/sparse-server/style/beacons/_configs.scss @@ -1,5 +1,6 @@ main.beacons div.config { padding: 10px; + overflow-y: scroll; fieldset { display: grid; diff --git a/sparse-server/style/beacons/_listeners.scss b/sparse-server/style/beacons/_listeners.scss index d63b2fe..daa3358 100644 --- a/sparse-server/style/beacons/_listeners.scss +++ b/sparse-server/style/beacons/_listeners.scss @@ -1,4 +1,6 @@ main.beacons div.listeners { + overflow-y: scroll; + form, p, h2 { margin: 10px; } diff --git a/sparse-server/style/beacons/_templates.scss b/sparse-server/style/beacons/_templates.scss index 6a92d05..86fe52e 100644 --- a/sparse-server/style/beacons/_templates.scss +++ b/sparse-server/style/beacons/_templates.scss @@ -1,5 +1,6 @@ main.beacons div.templates { padding: 10px; + overflow-y: scroll; fieldset { display: grid; diff --git a/sparse-server/style/main.scss b/sparse-server/style/main.scss index abfc2ea..694d930 100644 --- a/sparse-server/style/main.scss +++ b/sparse-server/style/main.scss @@ -70,7 +70,11 @@ main { input[type="submit"], input[type="button"], -button { +button, +a.button { + font-family: sans-serif; + font-size: 0.9rem; + color: black; background-color: #fff; border: 1px solid black; cursor: pointer; @@ -82,4 +86,13 @@ button { &:hover { background-color: #ccc; } + + &.warning { + background-color: #f00; + color: white; + + &:hover { + background-color: #c00; + } + } } diff --git a/unix-loader/src/abi.h b/unix-loader/src/abi.h index 34fd88c..e310943 100644 --- a/unix-loader/src/abi.h +++ b/unix-loader/src/abi.h @@ -10,12 +10,12 @@ typedef struct { typedef union SourceIp { struct { char mode; // set to 0 - char source_mac[6]; + unsigned char source_mac[6]; ipaddr_t source_ip; } use_host_networking; struct { char mode; // set to 1 - char source_mac[6]; + unsigned char source_mac[6]; unsigned short netmask; ipaddr_t source_ip; ipaddr_t gateway; @@ -27,12 +27,17 @@ typedef struct Parameters { SourceIp_t source_ip; unsigned short destination_port; unsigned short pubkey_cert_size; - unsigned short template_name_length; unsigned short domain_name_length; unsigned short beacon_name_length; - char pubkey_cert[1024]; - char beacon_identifier[64]; - char template_name[128]; - char domain_name[128]; - char beacon_name[128]; + unsigned short client_key_length; + unsigned short client_cert_length; + unsigned short template_id; + unsigned char delay_seconds_min; + unsigned char delay_seconds_max; + unsigned char pubkey_cert[512]; + unsigned char client_key[256]; + unsigned char client_cert[384]; + unsigned char beacon_identifier[64]; + unsigned char domain_name[64]; + unsigned char beacon_name[128]; } Parameters_t;