From d40efad46f3a7f53430d5a0ea083128ebb31d233 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Mon, 30 Sep 2024 15:45:48 -0500 Subject: [PATCH 01/12] ngwaf-compute-integration --- .gitignore | 3 +- ngwaf-compute-integration/.cargo/config.toml | 2 + ngwaf-compute-integration/.gitignore | 4 + ngwaf-compute-integration/Cargo.lock | 460 ++++++++++++++++++ ngwaf-compute-integration/Cargo.toml | 13 + ngwaf-compute-integration/README.md | 8 + ngwaf-compute-integration/fastly.toml | 44 ++ ngwaf-compute-integration/rust-toolchain.toml | 3 + ngwaf-compute-integration/src/main.rs | 167 +++++++ 9 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 ngwaf-compute-integration/.cargo/config.toml create mode 100644 ngwaf-compute-integration/.gitignore create mode 100644 ngwaf-compute-integration/Cargo.lock create mode 100644 ngwaf-compute-integration/Cargo.toml create mode 100644 ngwaf-compute-integration/README.md create mode 100644 ngwaf-compute-integration/fastly.toml create mode 100644 ngwaf-compute-integration/rust-toolchain.toml create mode 100644 ngwaf-compute-integration/src/main.rs diff --git a/.gitignore b/.gitignore index 4cbfdab..8fe531d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ override.tf.json # example: *tfplan* gold-standard-starter/.terraform.lock.hcl ngwaf-terraform-edge-deploy/terraform.tfvars -local_lab/* \ No newline at end of file +local_lab/* +ngwaf-compute-integration/latency_testing.sh diff --git a/ngwaf-compute-integration/.cargo/config.toml b/ngwaf-compute-integration/.cargo/config.toml new file mode 100644 index 0000000..6b77899 --- /dev/null +++ b/ngwaf-compute-integration/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" diff --git a/ngwaf-compute-integration/.gitignore b/ngwaf-compute-integration/.gitignore new file mode 100644 index 0000000..9f6a089 --- /dev/null +++ b/ngwaf-compute-integration/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +/bin +/pkg diff --git a/ngwaf-compute-integration/Cargo.lock b/ngwaf-compute-integration/Cargo.lock new file mode 100644 index 0000000..d3fd5df --- /dev/null +++ b/ngwaf-compute-integration/Cargo.lock @@ -0,0 +1,460 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "elsa" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98e71ae4df57d214182a2e5cb90230c0192c6ddfcaa05c36453d46a54713e10" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "fastly" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291f8c77eadfb49f5c258263c4d020143ac00d8b6170cd0c3e8c2e7f15ef2b61" +dependencies = [ + "anyhow", + "bytes", + "elsa", + "fastly-macros", + "fastly-shared", + "fastly-sys", + "http", + "lazy_static", + "mime", + "serde", + "serde_json", + "serde_urlencoded", + "sha2", + "smallvec", + "thiserror", + "time", + "url", +] + +[[package]] +name = "fastly-compute-project" +version = "0.1.0" +dependencies = [ + "fastly", +] + +[[package]] +name = "fastly-macros" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318a46d3977f84c106a295b232f73c6553b6c61cfcfd34aa487038e495486285" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fastly-shared" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3d3fae2a497c92302b8f86365c26027345800ead360c758ed6dc25f743fbde" +dependencies = [ + "bitflags", + "http", +] + +[[package]] +name = "fastly-sys" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a916457afd05221a21c766a4d5f8a32189bc337da45fff5f2ec073170b4985" +dependencies = [ + "bitflags", + "fastly-shared", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/ngwaf-compute-integration/Cargo.toml b/ngwaf-compute-integration/Cargo.toml new file mode 100644 index 0000000..acae1e1 --- /dev/null +++ b/ngwaf-compute-integration/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fastly-compute-project" +version = "0.1.0" +edition = "2021" +# Remove this line if you want to be able to publish this crate as open source on crates.io. +# Otherwise, `publish = false` prevents an accidental `cargo publish` from revealing private source. +publish = false + +[profile.release] +debug = 1 + +[dependencies] +fastly = "0.10.5" diff --git a/ngwaf-compute-integration/README.md b/ngwaf-compute-integration/README.md new file mode 100644 index 0000000..0cd193f --- /dev/null +++ b/ngwaf-compute-integration/README.md @@ -0,0 +1,8 @@ +# Empty Starter Kit for Rust + +An empty Rust project template for Fastly Compute programs that returns an empty `200 OK` response for +any request it receives. + +## Security issues + +Please see [SECURITY.md](SECURITY.md) for guidance on reporting security-related issues. diff --git a/ngwaf-compute-integration/fastly.toml b/ngwaf-compute-integration/fastly.toml new file mode 100644 index 0000000..fd2f29c --- /dev/null +++ b/ngwaf-compute-integration/fastly.toml @@ -0,0 +1,44 @@ +# This file describes a Fastly Compute package. To learn more visit: +# https://www.fastly.com/documentation/reference/compute/fastly-toml + +authors = [""] +cloned_from = "https://github.com/fastly/compute-starter-kit-rust-empty" +description = "" +language = "rust" +manifest_version = 3 +name = "ngwaf-compute-integration" +service_id = "" + +[scripts] + build = "cargo build --bin fastly-compute-project --release --target wasm32-wasi --color always" + +[setup] + + [setup.backends] + + [setup.backends.HTTPME] + address = "http-me.edgecompute.app" + port = 443 + + [setup.config_stores] + + [setup.config_stores.ngwaf] + description = "Next-gen WAF configuration" + + [setup.config_stores.ngwaf.items] + + [setup.config_stores.ngwaf.items.corp] + + [setup.config_stores.ngwaf.items.site] + +[local_server] + [local_server.backends] + [local_server.backends.HTTPME] + url = "https://http.edgecompute.app" + + [local_server.config_stores] + [local_server.config_stores.ngwaf] + format = "inline-toml" + [local_server.config_stores.ngwaf.contents] + "corp" = "" + "site" = "" diff --git a/ngwaf-compute-integration/rust-toolchain.toml b/ngwaf-compute-integration/rust-toolchain.toml new file mode 100644 index 0000000..5914a56 --- /dev/null +++ b/ngwaf-compute-integration/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +targets = [ "wasm32-wasi" ] diff --git a/ngwaf-compute-integration/src/main.rs b/ngwaf-compute-integration/src/main.rs new file mode 100644 index 0000000..fe37357 --- /dev/null +++ b/ngwaf-compute-integration/src/main.rs @@ -0,0 +1,167 @@ +use fastly::experimental::{inspect, InspectConfig, InspectError, InspectResponse}; +use fastly::handle::BodyHandle; +use fastly::http::{HeaderValue, StatusCode}; +use fastly::{Backend, Error, Request, Response}; +use std::time::Duration; + +const HTTPME_BACKEND: &str = "HTTPME"; + +#[fastly::main] +fn main(mut req: Request) -> Result { + // Log service version + println!( + "FASTLY_SERVICE_VERSION: {}", + std::env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| String::new()) + ); + + // Do not cache requests + req.set_pass(true); + + // Returns req to allow for setting request headers based on the WAF inspection. + let (mut req, waf_inspection_result) = do_waf_inspect(req); + + // if the waf inspection has an error, then return the error. + if waf_inspection_result.get_status() != 200 { + return Ok(waf_inspection_result); + } + + // if the header target-backend is set then try to construct and send the request to the backend. + if req.get_header_str("target-host").is_some(){ + let resp = dynamic_backend(req)?; + return Ok(resp) + } + + req.set_header("host", "http.edgecompute.app"); + + // update + + return Ok(req.send(HTTPME_BACKEND)?) +} + +// http https://ngwaf-compute.edgecompute.app -p=bh + +fn do_waf_inspect(mut req: Request) -> (Request, Response) { + // if bypass-waf is present, then do not send the request to the WAF for processing + match req.get_header_str("bypass-waf") { + Some(_) => { + println!("bypassing waf"); + return ( + req, + Response::from_status(StatusCode::OK).with_set_header( + "x-version", + std::env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| String::new()), + ), + ); + } + _ => { + let ngwaf_config = fastly::config_store::ConfigStore::open("ngwaf"); + let corp_name = ngwaf_config + .get("corp") + .expect("no `corp` present in config"); + let site_name = ngwaf_config + .get("site") + .expect("no `site` present in config"); + + // clone the request and send the cloned request to the waf inspection. + let inspection_req = req.clone_with_body(); + let (reqhandle, bodyhandle) = inspection_req.into_handles(); + let bodyhandle = bodyhandle.unwrap_or_else(|| BodyHandle::new()); + + let inspectconf: InspectConfig<'_> = InspectConfig::new(&reqhandle, &bodyhandle) + .corp(&corp_name) + .workspace(&site_name); + let waf_result: Result = inspect(inspectconf); + + match waf_result { + Ok(x) => { + // Handling WAF result + println!( + "waf_status_code: {}\nwaf_tags: {:?}\nwaf_decision_ms: {:?}\nwaf_verdict: {:?}", + x.status(), + x.tags(), + x.decision_ms(), + x.verdict(), + ); + req.set_header( + "waf-status", + HeaderValue::from_str(&x.status().to_string()).unwrap(), + ); + req.set_header( + "waf-tags", + HeaderValue::from_str( + x.tags() + .into_iter() + .collect::>() + .join(", ") + .as_str(), + ) + .unwrap(), + ); + req.set_header( + "waf-decision-ms", + HeaderValue::from_str(format!("{:?}", &x.decision_ms()).as_str()).unwrap(), + ); + req.set_header( + "waf-verdict", + HeaderValue::from_str(format!("{:?}", &x.verdict()).as_str()).unwrap(), + ); + } + Err(y) => match y { + InspectError::InvalidConfig => { + println!("NGWAF failed because of invalid configuration"); + return ( + req, + Response::from_status(StatusCode::SERVICE_UNAVAILABLE).with_set_header( + "x-version", + std::env::var("FASTLY_SERVICE_VERSION") + .unwrap_or_else(|_| String::new()), + ), + ); + } + InspectError::RequestError(f) => { + println!( + "Failed to send an inspection request to the NGWAF FastlyStatusCode: {}", + f.code + ); + return ( + req, + Response::from_status(StatusCode::SERVICE_UNAVAILABLE).with_set_header( + "x-version", + std::env::var("FASTLY_SERVICE_VERSION") + .unwrap_or_else(|_| String::new()), + ), + ); + } + _ => println!("Catch-all waf_result"), + }, + }; + + return ( + req, + Response::from_status(StatusCode::OK).with_set_header( + "x-version", + std::env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| String::new()), + ), + ); + } + } +} + +fn dynamic_backend(req: Request) -> Result { + // Extract backend, headers, and repeat values from the parsed body + let target_host = req.get_header_str("target-host").unwrap_or("http.edgecompute.app"); + + // Dynamic backend builder + let target_backend = Backend::builder(target_host, target_host) + .override_host(&target_host) + .connect_timeout(Duration::from_secs(1)) + .first_byte_timeout(Duration::from_secs(15)) + .between_bytes_timeout(Duration::from_secs(10)) + .enable_ssl() + .sni_hostname(target_host) + .override_host(target_host) + .finish()?; + + // Return the final response + Ok(req.send(target_backend)?) +} From bb1d9d37510d48ef6a70a603423d4a75bdc7fa98 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Thu, 3 Oct 2024 11:22:31 -0500 Subject: [PATCH 02/12] Updates --- ngwaf-compute-integration/README.md | 8 ++----- ngwaf-compute-integration/src/main.rs | 33 ++++----------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/ngwaf-compute-integration/README.md b/ngwaf-compute-integration/README.md index 0cd193f..6737f8f 100644 --- a/ngwaf-compute-integration/README.md +++ b/ngwaf-compute-integration/README.md @@ -1,8 +1,4 @@ -# Empty Starter Kit for Rust +# Next-Gen WAF in Compute -An empty Rust project template for Fastly Compute programs that returns an empty `200 OK` response for -any request it receives. +Pass requests to Fastly's Next-Gen Web Application Firewall (Next-Gen WAF) from Compute code and make decisions based on the analysis response. -## Security issues - -Please see [SECURITY.md](SECURITY.md) for guidance on reporting security-related issues. diff --git a/ngwaf-compute-integration/src/main.rs b/ngwaf-compute-integration/src/main.rs index fe37357..44a064e 100644 --- a/ngwaf-compute-integration/src/main.rs +++ b/ngwaf-compute-integration/src/main.rs @@ -25,21 +25,15 @@ fn main(mut req: Request) -> Result { return Ok(waf_inspection_result); } - // if the header target-backend is set then try to construct and send the request to the backend. - if req.get_header_str("target-host").is_some(){ - let resp = dynamic_backend(req)?; - return Ok(resp) - } - req.set_header("host", "http.edgecompute.app"); - // update + // Send request to the backend + let resp: Response = req.send(HTTPME_BACKEND)?; - return Ok(req.send(HTTPME_BACKEND)?) + // Return the response back to the client + return Ok(resp) } -// http https://ngwaf-compute.edgecompute.app -p=bh - fn do_waf_inspect(mut req: Request) -> (Request, Response) { // if bypass-waf is present, then do not send the request to the WAF for processing match req.get_header_str("bypass-waf") { @@ -146,22 +140,3 @@ fn do_waf_inspect(mut req: Request) -> (Request, Response) { } } } - -fn dynamic_backend(req: Request) -> Result { - // Extract backend, headers, and repeat values from the parsed body - let target_host = req.get_header_str("target-host").unwrap_or("http.edgecompute.app"); - - // Dynamic backend builder - let target_backend = Backend::builder(target_host, target_host) - .override_host(&target_host) - .connect_timeout(Duration::from_secs(1)) - .first_byte_timeout(Duration::from_secs(15)) - .between_bytes_timeout(Duration::from_secs(10)) - .enable_ssl() - .sni_hostname(target_host) - .override_host(target_host) - .finish()?; - - // Return the final response - Ok(req.send(target_backend)?) -} From 89d6736adf25e47a95ca546d25da91cbe1a90a0c Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Thu, 3 Oct 2024 11:42:28 -0500 Subject: [PATCH 03/12] Create build-ngwaf-compute-integration.yaml --- .../build-ngwaf-compute-integration.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/build-ngwaf-compute-integration.yaml diff --git a/.github/workflows/build-ngwaf-compute-integration.yaml b/.github/workflows/build-ngwaf-compute-integration.yaml new file mode 100644 index 0000000..33716e0 --- /dev/null +++ b/.github/workflows/build-ngwaf-compute-integration.yaml @@ -0,0 +1,22 @@ +name: Build ngwaf-compute-integration +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Fastly CLI + uses: fastly/compute-actions/setup@v8 + with: + cli_version: '1.0.0' # optional, defaults to 'latest' + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Compute Package + uses: fastly/compute-actions/build@v8 + with: + verbose: true # optionally enables verbose output, defaults to false + project_directory: ./ngwaf-compute-integration From 9b26078fd744ce4529013b4b128f2b35790ca93b Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Thu, 3 Oct 2024 11:46:27 -0500 Subject: [PATCH 04/12] Update build-ngwaf-compute-integration.yaml --- .github/workflows/build-ngwaf-compute-integration.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-ngwaf-compute-integration.yaml b/.github/workflows/build-ngwaf-compute-integration.yaml index 33716e0..6749386 100644 --- a/.github/workflows/build-ngwaf-compute-integration.yaml +++ b/.github/workflows/build-ngwaf-compute-integration.yaml @@ -1,7 +1,5 @@ name: Build ngwaf-compute-integration -on: - push: - branches: [main] +on: push jobs: deploy: From 5e9f6f256beba3d512743ddb442ea209d953f459 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Thu, 3 Oct 2024 11:48:24 -0500 Subject: [PATCH 05/12] Update build-ngwaf-compute-integration.yaml --- .github/workflows/build-ngwaf-compute-integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ngwaf-compute-integration.yaml b/.github/workflows/build-ngwaf-compute-integration.yaml index 6749386..858ea71 100644 --- a/.github/workflows/build-ngwaf-compute-integration.yaml +++ b/.github/workflows/build-ngwaf-compute-integration.yaml @@ -10,7 +10,7 @@ jobs: - name: Set up Fastly CLI uses: fastly/compute-actions/setup@v8 with: - cli_version: '1.0.0' # optional, defaults to 'latest' + cli_version: '10.14.1' # optional, defaults to 'latest' token: ${{ secrets.GITHUB_TOKEN }} - name: Build Compute Package From 900fea1fa568752806c6676dbe817e1d1d916972 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Thu, 3 Oct 2024 11:50:26 -0500 Subject: [PATCH 06/12] Update build-ngwaf-compute-integration.yaml --- .github/workflows/build-ngwaf-compute-integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ngwaf-compute-integration.yaml b/.github/workflows/build-ngwaf-compute-integration.yaml index 858ea71..8ca3bbc 100644 --- a/.github/workflows/build-ngwaf-compute-integration.yaml +++ b/.github/workflows/build-ngwaf-compute-integration.yaml @@ -2,7 +2,7 @@ name: Build ngwaf-compute-integration on: push jobs: - deploy: + build-ngwaf-compute-integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From ebc0e22fd2113f6967401062d3159246a6a1fa26 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Thu, 3 Oct 2024 11:51:49 -0500 Subject: [PATCH 07/12] Update main.rs --- ngwaf-compute-integration/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ngwaf-compute-integration/src/main.rs b/ngwaf-compute-integration/src/main.rs index 44a064e..37e3ab5 100644 --- a/ngwaf-compute-integration/src/main.rs +++ b/ngwaf-compute-integration/src/main.rs @@ -1,8 +1,7 @@ use fastly::experimental::{inspect, InspectConfig, InspectError, InspectResponse}; use fastly::handle::BodyHandle; use fastly::http::{HeaderValue, StatusCode}; -use fastly::{Backend, Error, Request, Response}; -use std::time::Duration; +use fastly::{Request, Response}; const HTTPME_BACKEND: &str = "HTTPME"; From 9285d3cfd07d832703ef1067bf46b9e0673fbe5a Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Fri, 4 Oct 2024 08:13:58 -0500 Subject: [PATCH 08/12] Update .gitignore --- ngwaf-compute-integration/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ngwaf-compute-integration/.gitignore b/ngwaf-compute-integration/.gitignore index 9f6a089..61e902a 100644 --- a/ngwaf-compute-integration/.gitignore +++ b/ngwaf-compute-integration/.gitignore @@ -2,3 +2,6 @@ **/*.rs.bk /bin /pkg + + +fastly.toml \ No newline at end of file From 56ec46ebc4381fd57261e0df1db6093d717c73fb Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Fri, 4 Oct 2024 08:15:00 -0500 Subject: [PATCH 09/12] commit --- .gitignore | 1 + ngwaf-compute-integration/.gitignore | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8fe531d..debdd49 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ gold-standard-starter/.terraform.lock.hcl ngwaf-terraform-edge-deploy/terraform.tfvars local_lab/* ngwaf-compute-integration/latency_testing.sh +ngwaf-compute-integration/fastly.toml diff --git a/ngwaf-compute-integration/.gitignore b/ngwaf-compute-integration/.gitignore index 61e902a..57b829a 100644 --- a/ngwaf-compute-integration/.gitignore +++ b/ngwaf-compute-integration/.gitignore @@ -4,4 +4,3 @@ /pkg -fastly.toml \ No newline at end of file From d78fa942d2befd5fb6631f01ab37e1428772fd24 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Fri, 4 Oct 2024 09:50:05 -0500 Subject: [PATCH 10/12] Update README.md --- ngwaf-compute-integration/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ngwaf-compute-integration/README.md b/ngwaf-compute-integration/README.md index 6737f8f..8d13d4f 100644 --- a/ngwaf-compute-integration/README.md +++ b/ngwaf-compute-integration/README.md @@ -2,3 +2,5 @@ Pass requests to Fastly's Next-Gen Web Application Firewall (Next-Gen WAF) from Compute code and make decisions based on the analysis response. +This implementation is inspired by the following documentation. +https://www.fastly.com/documentation/solutions/tutorials/next-gen-waf-compute/ From a7d74ca0b1f151bb8d43da6b8bc9a10bef4f94a7 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Tue, 8 Oct 2024 10:26:44 -0400 Subject: [PATCH 11/12] Update README.md --- ngwaf-compute-integration/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngwaf-compute-integration/README.md b/ngwaf-compute-integration/README.md index 8d13d4f..07d0f80 100644 --- a/ngwaf-compute-integration/README.md +++ b/ngwaf-compute-integration/README.md @@ -1,6 +1,6 @@ # Next-Gen WAF in Compute -Pass requests to Fastly's Next-Gen Web Application Firewall (Next-Gen WAF) from Compute code and make decisions based on the analysis response. +Pass requests to Fastly's Next-Gen Web Application Firewall (Next-Gen WAF) from Compute code and make decisions based on the WAF analysis response. This implementation is inspired by the following documentation. https://www.fastly.com/documentation/solutions/tutorials/next-gen-waf-compute/ From b316978674b1ddc66bf5d451401cb33706db5924 Mon Sep 17 00:00:00 2001 From: Brooks Cunningham Date: Tue, 8 Oct 2024 14:01:06 -0400 Subject: [PATCH 12/12] Updates --- .gitignore | 1 + ngwaf-compute-integration/src/main.rs | 67 +++++++++++++++------------ 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index debdd49..d602188 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ ngwaf-terraform-edge-deploy/terraform.tfvars local_lab/* ngwaf-compute-integration/latency_testing.sh ngwaf-compute-integration/fastly.toml +ngwaf-compute-integration/fastly.toml diff --git a/ngwaf-compute-integration/src/main.rs b/ngwaf-compute-integration/src/main.rs index 37e3ab5..5893e90 100644 --- a/ngwaf-compute-integration/src/main.rs +++ b/ngwaf-compute-integration/src/main.rs @@ -2,6 +2,7 @@ use fastly::experimental::{inspect, InspectConfig, InspectError, InspectResponse use fastly::handle::BodyHandle; use fastly::http::{HeaderValue, StatusCode}; use fastly::{Request, Response}; +use std::collections::HashMap; const HTTPME_BACKEND: &str = "HTTPME"; @@ -30,7 +31,7 @@ fn main(mut req: Request) -> Result { let resp: Response = req.send(HTTPME_BACKEND)?; // Return the response back to the client - return Ok(resp) + return Ok(resp); } fn do_waf_inspect(mut req: Request) -> (Request, Response) { @@ -66,38 +67,18 @@ fn do_waf_inspect(mut req: Request) -> (Request, Response) { let waf_result: Result = inspect(inspectconf); match waf_result { - Ok(x) => { + Ok(inspect_resp) => { // Handling WAF result println!( "waf_status_code: {}\nwaf_tags: {:?}\nwaf_decision_ms: {:?}\nwaf_verdict: {:?}", - x.status(), - x.tags(), - x.decision_ms(), - x.verdict(), - ); - req.set_header( - "waf-status", - HeaderValue::from_str(&x.status().to_string()).unwrap(), - ); - req.set_header( - "waf-tags", - HeaderValue::from_str( - x.tags() - .into_iter() - .collect::>() - .join(", ") - .as_str(), - ) - .unwrap(), - ); - req.set_header( - "waf-decision-ms", - HeaderValue::from_str(format!("{:?}", &x.decision_ms()).as_str()).unwrap(), - ); - req.set_header( - "waf-verdict", - HeaderValue::from_str(format!("{:?}", &x.verdict()).as_str()).unwrap(), + inspect_resp.status(), + inspect_resp.tags(), + inspect_resp.decision_ms(), + inspect_resp.verdict(), ); + let waf_inspection_header_val = format_waf_inspection_header(inspect_resp); + + req.set_header("waf-inspect-data", waf_inspection_header_val); } Err(y) => match y { InspectError::InvalidConfig => { @@ -139,3 +120,31 @@ fn do_waf_inspect(mut req: Request) -> (Request, Response) { } } } + +fn format_waf_inspection_header(inspect_resp: InspectResponse) -> String { + // Inspired by https://www.fastly.com/documentation/solutions/examples/filter-cookies-or-other-structured-headers/ + + println!("Inspection Response: {:?}", inspect_resp); + + let mut filtered_cookie_header_value = "".to_string(); + + filtered_cookie_header_value.push_str(&format!("{}{:?};", "status=", inspect_resp.status())); + filtered_cookie_header_value.push_str(&format!( + "{}{};", + "tags=", + inspect_resp + .tags() + .into_iter() + .collect::>() + .join(",") + .as_str() + )); + filtered_cookie_header_value.push_str(&format!( + "{}{:?};", + "decision_ms=", + inspect_resp.decision_ms() + )); + filtered_cookie_header_value.push_str(&format!("{}{:?}", "verdict=", inspect_resp.verdict())); + + return filtered_cookie_header_value; +}