diff --git a/Cargo.lock b/Cargo.lock index 7d81f21..0bde92c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,6 +38,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "backtrace" version = "0.3.74" @@ -122,6 +146,25 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + [[package]] name = "hybrid-array" version = "0.3.0" @@ -161,6 +204,7 @@ dependencies = [ "realm_io", "realm_syscall", "regex", + "saphyr", "sha2", "sha3", "static_cell", @@ -197,6 +241,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -215,7 +265,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.24", ] [[package]] @@ -323,6 +373,27 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "saphyr" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31513a748cdf8fb8d1b64dcc14fdd997fa02f2431671c7ce34efbea5e7eeea" +dependencies = [ + "arraydeque", + "hashlink", + "saphyr-parser", +] + +[[package]] +name = "saphyr-parser" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123f0a6da68f3072c7c761450276d1d444cb391c8be182a757cd26cf684cb77f" +dependencies = [ + "arraydeque", + "hashlink", +] + [[package]] name = "sha2" version = "0.11.0-pre.5" @@ -419,6 +490,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -498,13 +575,33 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.24", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 17e99b0..c119268 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ rand = "0.8.5" realm_io = { version = "0.5" } realm_syscall = "0.1" regex = { version = "1.11", default-features = false, features = ["perf", "std"] } +saphyr = { version = "0.0.3", default-features = false } sha2 = { version = "0.11.0-pre.5", default-features = false, features = ["std"] } # TODO test feature asm on ARM sha3 = { version = "0.11.0-pre.5", default-features = false, features = ["std"] } diff --git a/README.md b/README.md index dd3dac2..5740f5d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mesozoa -Intermediate reverse proxy that protects your server from crawlers by forcing the browser to run proof of work. +Mesozoa is a small animal living between a reverse-proxy and a server, protecting the server from crawlers by forcing the browser to run proof of work. ## Why? diff --git a/example-config.yaml b/example-config.yaml new file mode 100644 index 0000000..61fa6c0 --- /dev/null +++ b/example-config.yaml @@ -0,0 +1,38 @@ +--- +# Mesozoa listens to this address +# It should be a local address, that receives traffic from the reverse-proxy. +listen: 127.0.0.1:8504 +# Mesozoa relays accepted traffic to this address +pass: 127.0.0.1:8000 + +# Duration (in seconds) of validity of a challenge +challenge-timeout: 3600 +# PoW difficulty +# Mean PoW duration is multiplied by 2 when this number is incremented by 1. +# (TODO: unused for now, needs dynamic challenge page) +#target-zeros: 15 + +# Action applied when no policy matches +# (see below for legal values) +default-action: challenge + +# Policy groups are evaluated in order. +# The first matching group stops evaluation. +# Policies within a group are all evaluated at the same time. +# The first matching policy (order from the list here) is applied and stops evaluation. +# `name` is for logging, put anything you want. +# `first-line` is a regex for the first line of the HTTP request. +# `action` is one of the following: +# - "allow": relay without challenge +# - "challenge": ask Pow challenge and relay iff client succeeds +# - "drop": drop connection and do not relay +policy-groups: +- - name: Favicon + first-line: "^GET /favicon.ico " + action: allow + - name: robots.txt + first-line: "^GET /robots.txt " + action: allow + - name: Favicon + first-line: "^GET /favicon.ico " + action: allow diff --git a/src/main.rs b/src/main.rs index 5ef6cf6..522a25b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,12 @@ use policy::{CompiledPolicies, Policy}; use rand::Rng; use realm_syscall::socket2::TcpKeepalive; use regex::bytes::Regex; -use std::{io::Write, net::SocketAddr, time::Duration}; +use std::{ + io::{Read, Write}, + net::SocketAddr, + str::FromStr, + time::Duration, +}; use tokio::{ io::{AsyncWriteExt, ReadBuf}, net::{TcpSocket, TcpStream}, @@ -34,28 +39,38 @@ macro_rules! mk_static { #[tokio::main] async fn main() { + let mut config_file = + std::fs::File::open("example-config.yaml").expect("Cannot open config file"); + let mut config_str = String::new(); + config_file + .read_to_string(&mut config_str) + .expect("Cannot read config file"); + let config_yaml = saphyr::Yaml::load_from_str(&config_str).expect("Error parsing config"); + let config_doc = &config_yaml[0]; + let listen_addr: SocketAddr = config_doc["listen"] + .as_str() + .expect("Missing listen address in config") + .parse() + .expect("Invalid listen address"); + let pass_addr: SocketAddr = config_doc["pass"] + .as_str() + .expect("Missing pass address in config") + .parse() + .expect("Invalid pass address"); + let default_action = policy::Action::from_str( + config_doc["default-action"] + .as_str() + .expect("Missing default action in config"), + ) + .expect("Invalid default action"); + let policy_groups: Vec> = config_doc["policy-groups"].as_vec().expect("Missing policies in config").into_iter().map(|policy_group| policy_group.as_vec().expect("Missing policies in config").into_iter().map(|policy| Policy { + name: policy["name"].as_str().expect("Expected policy name string").to_string(), + first_line_regex: policy["first-line"].as_str().expect("Expected policy first line regex string").to_string(), + action: policy::Action::from_str(policy["action"].as_str().expect("Expected policy action string")).expect("Invalid policy action"), + }).collect()).collect(); + let mut rng = rand::thread_rng(); - let listen_addr: SocketAddr = "127.0.0.1:8504".parse().unwrap(); - let pass_addr: SocketAddr = "127.0.0.1:80".parse().unwrap(); - let policy_groups = vec![vec![ - Policy { - name: String::from("Favicon"), - first_line_regex: String::from(r"^GET /favicon.ico"), - action: policy::Action::Allow, - }, - Policy { - name: String::from("robots.txt"), - first_line_regex: String::from(r"^GET /robots.txt"), - action: policy::Action::Allow, - }, - Policy { - name: String::from("Block"), - first_line_regex: String::from(r"^GET /block"), - action: policy::Action::Drop, - }, - ]]; - let default_action = policy::Action::Challenge; let secret: [u8; SECRET_LEN] = rng.r#gen(); let policy_groups = &*mk_static!( diff --git a/src/policy.rs b/src/policy.rs index c80ec2e..1c796e0 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -7,6 +7,18 @@ pub enum Action { Drop, } +impl std::str::FromStr for Action { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "allow" | "Allow" => Ok(Self::Allow), + "challenge" | "Challenge" => Ok(Self::Challenge), + "drop" | "Drop" => Ok(Self::Drop), + _ => Err(()), + } + } +} + #[derive(Clone, Debug)] pub struct Policy { pub name: String,