From 4cf052b10993e4c5d3559d8c41b8b9a8635f607f Mon Sep 17 00:00:00 2001 From: "wangbaiping(wbpcode)" Date: Mon, 7 Jul 2025 13:25:38 +0000 Subject: [PATCH 1/6] initial dirty commit Signed-off-by: wangbaiping(wbpcode) --- .gitignore | 2 + Cargo.toml | 2 + dynamic/rust/hello/Cargo.lock | 371 ++++++++++++++++++++++++++++++++++ dynamic/rust/hello/Cargo.toml | 16 ++ dynamic/rust/hello/src/lib.rs | 0 rustfmt.toml | 60 ++++++ 6 files changed, 451 insertions(+) create mode 100644 Cargo.toml create mode 100644 dynamic/rust/hello/Cargo.lock create mode 100644 dynamic/rust/hello/Cargo.toml create mode 100644 dynamic/rust/hello/src/lib.rs create mode 100644 rustfmt.toml diff --git a/.gitignore b/.gitignore index 6e63f07..c429e04 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ bazel.output.txt node_modules dist *.bak + +target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..54142f1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["dynamic/rust/hello"] diff --git a/dynamic/rust/hello/Cargo.lock b/dynamic/rust/hello/Cargo.lock new file mode 100644 index 0000000..7ce2f3b --- /dev/null +++ b/dynamic/rust/hello/Cargo.lock @@ -0,0 +1,371 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "envoy-proxy-dynamic-modules-rust-hello" +version = "0.0.1" +dependencies = [ + "envoy-proxy-dynamic-modules-rust-sdk", +] + +[[package]] +name = "envoy-proxy-dynamic-modules-rust-sdk" +version = "0.1.0" +source = "git+https://github.com/envoyproxy/envoy?rev=73fe00fc139fd5053f4c4a5d66569cc254449896#73fe00fc139fd5053f4c4a5d66569cc254449896" +dependencies = [ + "bindgen", + "mockall", +] + +[[package]] +name = "fragile" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/dynamic/rust/hello/Cargo.toml b/dynamic/rust/hello/Cargo.toml new file mode 100644 index 0000000..64b6eb2 --- /dev/null +++ b/dynamic/rust/hello/Cargo.toml @@ -0,0 +1,16 @@ + +[package] +name = "envoy-proxy-dynamic-modules-rust-hello" +version = "0.0.1" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/envoyproxy/modules" + +[dependencies] +# The SDK version must match the Envoy version due to the strict compatibility requirements. +envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "73fe00fc139fd5053f4c4a5d66569cc254449896" } + +[lib] +name = "hello_proxy" +path = "src/lib.rs" +crate-type = ["cdylib"] diff --git a/dynamic/rust/hello/src/lib.rs b/dynamic/rust/hello/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..f7abdfb --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,60 @@ +# This config is copied from https://github.com/bitdriftlabs/shared-core/blob/4114708cafb80103092b7e585987ef275a136f87/rustfmt.toml + +# Rust Autoformatter Config +# ========================= +# +# Note that most of rustfmt's configurable settings are nightly-only, +# so we tend to run that. Typically we don't rely on unstable features, +# but in this case there's honestly not a lot to configure that _isn't_ +# behind the `unstable_features` gate, unfortunately. + +edition = "2021" +unstable_features = true + +# Try to use inline annotations before using this -- disabling autoformatting +# for a file makes it harder for everyone to share their work in that file and +# invites bikeshedding about how code in there should look. +ignore = [] + +# Features are broken up into Stable and Unstable features, listed in order of +# appearance in the Configuring Rustfmt document: +# https://github.com/rust-lang-nursery/rustfmt/blob/master/Configurations.md + +## Stable Features + +force_explicit_abi = true # Default +hard_tabs = false # Default +max_width = 100 +merge_derives = true # Default +newline_style = "Auto" # Default +remove_nested_parens = true # Default +reorder_imports = true # Default +reorder_modules = true # Default +tab_spaces = 2 +use_field_init_shorthand = false # Default +use_small_heuristics = "Default" # Default +use_try_shorthand = true + +## Features that were "unstable" when we configured them +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 3 +combine_control_expr = false +comment_width = 100 +condense_wildcard_suffixes = true +enum_discrim_align_threshold = 20 +error_on_line_overflow = true +error_on_unformatted = false +format_code_in_doc_comments = true +format_macro_bodies = true +format_macro_matchers = true +format_strings = true +group_imports = "One" +imports_granularity = "Module" +imports_layout = "HorizontalVertical" +match_block_trailing_comma = true +normalize_comments = false +normalize_doc_attributes = true +skip_children = false +spaces_around_ranges = true +struct_field_align_threshold = 0 +wrap_comments = true From da0057a10e7fa9e7ae7ba1d7ea5e87ad1d718b32 Mon Sep 17 00:00:00 2001 From: "wangbaiping(wbpcode)" Date: Mon, 7 Jul 2025 13:27:54 +0000 Subject: [PATCH 2/6] initial dirty commit Signed-off-by: wangbaiping(wbpcode) --- Cargo.lock | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7ce2f3b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,371 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "envoy-proxy-dynamic-modules-rust-hello" +version = "0.0.1" +dependencies = [ + "envoy-proxy-dynamic-modules-rust-sdk", +] + +[[package]] +name = "envoy-proxy-dynamic-modules-rust-sdk" +version = "0.1.0" +source = "git+https://github.com/envoyproxy/envoy?rev=73fe00fc139fd5053f4c4a5d66569cc254449896#73fe00fc139fd5053f4c4a5d66569cc254449896" +dependencies = [ + "bindgen", + "mockall", +] + +[[package]] +name = "fragile" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" From 5cd6716eda018cdd4af95ace4fab4194d60d0d07 Mon Sep 17 00:00:00 2001 From: "wangbaiping(wbpcode)" Date: Tue, 8 Jul 2025 13:25:09 +0000 Subject: [PATCH 3/6] my first try of rust Signed-off-by: wangbaiping(wbpcode) --- Cargo.lock | 48 ++++- Cargo.toml | 2 +- dynamic/rust/hello/src/lib.rs | 0 .../rust/{hello => ip_restriction}/Cargo.lock | 0 .../rust/{hello => ip_restriction}/Cargo.toml | 6 +- dynamic/rust/ip_restriction/src/lib.rs | 177 ++++++++++++++++++ 6 files changed, 229 insertions(+), 4 deletions(-) delete mode 100644 dynamic/rust/hello/src/lib.rs rename dynamic/rust/{hello => ip_restriction}/Cargo.lock (100%) rename dynamic/rust/{hello => ip_restriction}/Cargo.toml (73%) create mode 100644 dynamic/rust/ip_restriction/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7ce2f3b..c406802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,10 +82,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "envoy-proxy-dynamic-modules-rust-hello" +name = "envoy-proxy-dynamic-modules-rust-ip-restriction" version = "0.0.1" dependencies = [ "envoy-proxy-dynamic-modules-rust-sdk", + "serde", + "serde_json", ] [[package]] @@ -118,6 +120,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "libc" version = "0.2.174" @@ -277,6 +285,44 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 54142f1..165bc8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["dynamic/rust/hello"] +members = ["dynamic/rust/ip_restriction"] diff --git a/dynamic/rust/hello/src/lib.rs b/dynamic/rust/hello/src/lib.rs deleted file mode 100644 index e69de29..0000000 diff --git a/dynamic/rust/hello/Cargo.lock b/dynamic/rust/ip_restriction/Cargo.lock similarity index 100% rename from dynamic/rust/hello/Cargo.lock rename to dynamic/rust/ip_restriction/Cargo.lock diff --git a/dynamic/rust/hello/Cargo.toml b/dynamic/rust/ip_restriction/Cargo.toml similarity index 73% rename from dynamic/rust/hello/Cargo.toml rename to dynamic/rust/ip_restriction/Cargo.toml index 64b6eb2..fd9c4f4 100644 --- a/dynamic/rust/hello/Cargo.toml +++ b/dynamic/rust/ip_restriction/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "envoy-proxy-dynamic-modules-rust-hello" +name = "envoy-proxy-dynamic-modules-rust-ip-restriction" version = "0.0.1" edition = "2021" license = "Apache-2.0" @@ -9,8 +9,10 @@ repository = "https://github.com/envoyproxy/modules" [dependencies] # The SDK version must match the Envoy version due to the strict compatibility requirements. envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "73fe00fc139fd5053f4c4a5d66569cc254449896" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [lib] -name = "hello_proxy" +name = "ip_restriction" path = "src/lib.rs" crate-type = ["cdylib"] diff --git a/dynamic/rust/ip_restriction/src/lib.rs b/dynamic/rust/ip_restriction/src/lib.rs new file mode 100644 index 0000000..a550b2a --- /dev/null +++ b/dynamic/rust/ip_restriction/src/lib.rs @@ -0,0 +1,177 @@ +use envoy_proxy_dynamic_modules_rust_sdk::*; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; +use std::str::FromStr; +use std::sync::Arc; + +// The raw filter config that will be deserialized from the JSON configuration. +// TODO(wbpcode): To support protobuf based API declaration in the future. +// TODO(wbpcode): to support ip range in the future. +#[derive(Serialize, Deserialize, Debug)] +pub struct RawFilterConfig { + denied_addresses: HashSet, + allowed_addresses: HashSet, +} + +#[derive(Debug)] +pub struct FilterConfigImpl { + denied_addresses_exact: HashSet, + allowed_addresses_exact: HashSet, +} + +// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] trait. +// +// The trait corresponds to a Envoy filter chain configuration. +#[derive(Debug, Clone)] +pub struct FilterConfig { + config: Arc, +} + +impl FilterConfig { + /// This is the constructor for the [`FilterConfig`]. + /// + /// filter_config is the filter config from the Envoy config here: + /// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig + pub fn new(filter_config: &str) -> Option { + let filter_config: RawFilterConfig = match serde_json::from_str(filter_config) { + Ok(cfg) => cfg, + Err(err) => { + eprintln!("Error parsing filter config: {err}"); + return None; + }, + }; + + // One and only one of denied_addresses and allowed_addresses should be set. + if filter_config.denied_addresses.is_empty() != filter_config.allowed_addresses.is_empty() { + eprintln!( + "Error parsing filter config: one and only one of denied_addresses\ + and allowed_addresses should be set" + ); + return None; + } + + let mut denied_addresses_exact = HashSet::new(); + let mut allowed_addresses_exact = HashSet::new(); + + // Validate every ip in the set is a valid IPv4 address or IPv6 address. + for ip in &filter_config.allowed_addresses { + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { + eprintln!("Error parsing ip in allowed_addresses: {ip}"); + return None; + } + allowed_addresses_exact.insert(ip.clone()); + } + for ip in &filter_config.denied_addresses { + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { + eprintln!("Error parsing ip in denied_addresses: {ip}"); + return None; + } + denied_addresses_exact.insert(ip.clone()); + } + + Some(FilterConfig { + config: Arc::new(FilterConfigImpl { + denied_addresses_exact: denied_addresses_exact, + allowed_addresses_exact: allowed_addresses_exact, + }), + }) + } +} + +impl HttpFilterConfig for FilterConfig { + /// This is called for each new HTTP filter. + fn new_http_filter(&mut self, _envoy: &mut EC) -> Box> { + Box::new(Filter { + filter_config: self.clone(), + }) + } +} + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. +/// +/// This sets the request and response headers to the values specified in the filter config. +pub struct Filter { + // The filter config have longer lifetime than the filter. + filter_config: FilterConfig, +} + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. +impl HttpFilter for Filter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + let downstream_addr = + envoy_filter.get_attribute_string(abi::envoy_dynamic_module_type_attribute_id::SourceAddress); + let downstream_port = + envoy_filter.get_attribute_int(abi::envoy_dynamic_module_type_attribute_id::SourcePort); + + if downstream_addr.is_none() || downstream_port.is_none() { + envoy_filter.send_response( + 403, + vec![], + Some(b"No remote address and request is forbidden."), + ); + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + } + + let mut downstream_addr_str = String::new(); + let address_buffer = downstream_addr.unwrap(); + let downstream_addr_slice = address_buffer.as_slice(); + + if downstream_port.is_none() { + // Covert the slice of downstream addr to string. + unsafe { + downstream_addr_str + .as_mut_vec() + .extend_from_slice(downstream_addr_slice); + } + } else { + // Strip the port from the downstream addr. + let downstream_addr_slice = &downstream_addr_slice + [0..downstream_addr_slice.len() - downstream_port.unwrap().to_string().len() - 1]; + + unsafe { + downstream_addr_str + .as_mut_vec() + .extend_from_slice(downstream_addr_slice); + } + } + + // Check if the downstream addr is in the allowed list. + if !self.filter_config.config.allowed_addresses_exact.is_empty() { + if !self + .filter_config + .config + .allowed_addresses_exact + .contains(&downstream_addr_str) + { + envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + } + } + + // Check if the downstream addr is in the denied list. + if !self.filter_config.config.denied_addresses_exact.is_empty() { + if self + .filter_config + .config + .denied_addresses_exact + .contains(&downstream_addr_str) + { + envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + } + } + + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +#[cfg(test)] +mod tests { + use super::*; +} From 8374a944656d566bf31c762f7d84157b4ce2087e Mon Sep 17 00:00:00 2001 From: "wangbaiping(wbpcode)" Date: Wed, 9 Jul 2025 09:45:21 +0000 Subject: [PATCH 4/6] complete test Signed-off-by: wangbaiping(wbpcode) --- dynamic/rust/ip_restriction/src/lib.rs | 198 ++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 2 deletions(-) diff --git a/dynamic/rust/ip_restriction/src/lib.rs b/dynamic/rust/ip_restriction/src/lib.rs index a550b2a..1897790 100644 --- a/dynamic/rust/ip_restriction/src/lib.rs +++ b/dynamic/rust/ip_restriction/src/lib.rs @@ -11,7 +11,9 @@ use std::sync::Arc; // TODO(wbpcode): to support ip range in the future. #[derive(Serialize, Deserialize, Debug)] pub struct RawFilterConfig { + #[serde(default)] denied_addresses: HashSet, + #[serde(default)] allowed_addresses: HashSet, } @@ -26,7 +28,7 @@ pub struct FilterConfigImpl { // The trait corresponds to a Envoy filter chain configuration. #[derive(Debug, Clone)] pub struct FilterConfig { - config: Arc, + config: Arc, // use Arc to make it is cheap to clone the FilterConfig. } impl FilterConfig { @@ -44,7 +46,7 @@ impl FilterConfig { }; // One and only one of denied_addresses and allowed_addresses should be set. - if filter_config.denied_addresses.is_empty() != filter_config.allowed_addresses.is_empty() { + if filter_config.denied_addresses.is_empty() == filter_config.allowed_addresses.is_empty() { eprintln!( "Error parsing filter config: one and only one of denied_addresses\ and allowed_addresses should be set" @@ -119,6 +121,7 @@ impl HttpFilter for Filter { } let mut downstream_addr_str = String::new(); + let address_buffer = downstream_addr.unwrap(); let downstream_addr_slice = address_buffer.as_slice(); @@ -174,4 +177,195 @@ impl HttpFilter for Filter { #[cfg(test)] mod tests { use super::*; + + #[test] + fn test_new_filter_config_both_set() { + let filter_config = FilterConfig::new( + r#"{ + "allowed_addresses": [ + "127.0.0.1", + "::1" + ], + "denied_addresses": [ + "192.168.1.1" + ] + }"#, + ); + assert!(filter_config.is_none()); // Only one of allowed_addresses and denied_addresses should be set. + } + + #[test] + fn test_new_filter_config_allowed_set() { + let filter_config = FilterConfig::new( + r#"{ + "allowed_addresses": [ + "127.0.0.1", + "::1" + ] + }"#, + ); + assert!(filter_config.is_some()); + } + + #[test] + fn test_new_filter_config_denied_set() { + let filter_config = FilterConfig::new( + r#"{ + "denied_addresses": [ + "192.168.1.1" + ] + }"#, + ); + assert!(filter_config.is_some()); + } + + #[test] + fn test_new_filter_config_invalid_ip() { + let filter_config = FilterConfig::new( + r#"{ + "allowed_addresses": [ + "127.0.0.1", + "invalid_ip" + ] + }"#, + ); + assert!(filter_config.is_none()); + } + + #[test] + fn test_filter_denied_because_no_address() { + let filter_config = FilterConfig::new( + r#"{ + "denied_addresses": [ + "192.168.1.1" + ] + }"#, + ); + assert!(filter_config.is_some()); + + let mut filter = Filter { + filter_config: filter_config.unwrap(), + }; + + let mut mock_envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| None); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| None); + mock_envoy_filter + .expect_send_response() + .times(1) + .returning(|code, _, _| assert!(code == 403)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + ); + } + + #[test] + fn test_filter_with_allowed_list() { + let filter_config = FilterConfig::new( + r#"{ + "allowed_addresses": [ + "127.0.0.1", + "::1" + ] + }"#, + ); + assert!(filter_config.is_some()); + + let mut filter = Filter { + filter_config: filter_config.unwrap(), + }; + + let mut mock_envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("127.0.0.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + ); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("192.168.1.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + mock_envoy_filter + .expect_send_response() + .times(1) + .returning(|code, _, _| assert!(code == 403)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + ); + } + + #[test] + fn test_filter_with_denied_list() { + let filter_config = FilterConfig::new( + r#"{ + "denied_addresses": [ + "192.168.1.1" + ] + }"#, + ); + assert!(filter_config.is_some()); + + let mut filter = Filter { + filter_config: filter_config.unwrap(), + }; + + let mut mock_envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("192.168.1.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + mock_envoy_filter + .expect_send_response() + .times(1) + .returning(|code, _, _| assert!(code == 403)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + ); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("127.0.0.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + ); + } } From b18163eee32c107903f3e80483fe9e0e137660ba Mon Sep 17 00:00:00 2001 From: "wangbaiping(wbpcode)" Date: Mon, 14 Jul 2025 04:03:46 +0000 Subject: [PATCH 5/6] add lint/test/build workflow Signed-off-by: wangbaiping(wbpcode) --- .github/workflows/commit.yml | 51 +++ dynamic/rust/ip_restriction/src/lib.rs | 575 +++++++++++++------------ rustfmt.toml | 60 --- 3 files changed, 339 insertions(+), 347 deletions(-) create mode 100644 .github/workflows/commit.yml delete mode 100644 rustfmt.toml diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml new file mode 100644 index 0000000..f9bb719 --- /dev/null +++ b/.github/workflows/commit.yml @@ -0,0 +1,51 @@ +name: Commit + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + dynamic_rust: + name: Dynamic Rust module lint/test/build on (${{ matrix.platform.arch }}) + runs-on: ${{ matrix.platform.os }} + strategy: + fail-fast: false + matrix: + platform: + - os: ubuntu-24.04 + arch: amd64 + - os: ubuntu-24.04-arm + arch: arm64 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Cache Cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ matrix.platform.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ matrix.platform.os }}-cargo-registry- + - name: Cache Cargo git index + uses: actions/cache@v4 + with: + path: ~/.cargo/git + key: ${{ matrix.platform.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ matrix.platform.os }}-cargo-git- + - name: Set up Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: clippy, rustfmt + - name: Check format + run: cargo fmt -- --check + - name: Run Clippy linter + run: cargo clippy -- -D warnings + - name: Build module + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/dynamic/rust/ip_restriction/src/lib.rs b/dynamic/rust/ip_restriction/src/lib.rs index 1897790..183b6be 100644 --- a/dynamic/rust/ip_restriction/src/lib.rs +++ b/dynamic/rust/ip_restriction/src/lib.rs @@ -11,16 +11,16 @@ use std::sync::Arc; // TODO(wbpcode): to support ip range in the future. #[derive(Serialize, Deserialize, Debug)] pub struct RawFilterConfig { - #[serde(default)] - denied_addresses: HashSet, - #[serde(default)] - allowed_addresses: HashSet, + #[serde(default)] + denied_addresses: HashSet, + #[serde(default)] + allowed_addresses: HashSet, } #[derive(Debug)] pub struct FilterConfigImpl { - denied_addresses_exact: HashSet, - allowed_addresses_exact: HashSet, + denied_addresses_exact: HashSet, + allowed_addresses_exact: HashSet, } // This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] trait. @@ -28,160 +28,158 @@ pub struct FilterConfigImpl { // The trait corresponds to a Envoy filter chain configuration. #[derive(Debug, Clone)] pub struct FilterConfig { - config: Arc, // use Arc to make it is cheap to clone the FilterConfig. + config: Arc, // use Arc to make it is cheap to clone the FilterConfig. } impl FilterConfig { - /// This is the constructor for the [`FilterConfig`]. - /// - /// filter_config is the filter config from the Envoy config here: - /// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig - pub fn new(filter_config: &str) -> Option { - let filter_config: RawFilterConfig = match serde_json::from_str(filter_config) { - Ok(cfg) => cfg, - Err(err) => { - eprintln!("Error parsing filter config: {err}"); - return None; - }, - }; - - // One and only one of denied_addresses and allowed_addresses should be set. - if filter_config.denied_addresses.is_empty() == filter_config.allowed_addresses.is_empty() { - eprintln!( - "Error parsing filter config: one and only one of denied_addresses\ + /// This is the constructor for the [`FilterConfig`]. + /// + /// filter_config is the filter config from the Envoy config here: + /// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig + pub fn new(filter_config: &str) -> Option { + let filter_config: RawFilterConfig = match serde_json::from_str(filter_config) { + Ok(cfg) => cfg, + Err(err) => { + eprintln!("Error parsing filter config: {err}"); + return None; + } + }; + + // One and only one of denied_addresses and allowed_addresses should be set. + if filter_config.denied_addresses.is_empty() == filter_config.allowed_addresses.is_empty() { + eprintln!( + "Error parsing filter config: one and only one of denied_addresses\ and allowed_addresses should be set" - ); - return None; + ); + return None; + } + + let mut denied_addresses_exact = HashSet::new(); + let mut allowed_addresses_exact = HashSet::new(); + + // Validate every ip in the set is a valid IPv4 address or IPv6 address. + for ip in &filter_config.allowed_addresses { + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { + eprintln!("Error parsing ip in allowed_addresses: {ip}"); + return None; + } + allowed_addresses_exact.insert(ip.clone()); + } + for ip in &filter_config.denied_addresses { + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { + eprintln!("Error parsing ip in denied_addresses: {ip}"); + return None; + } + denied_addresses_exact.insert(ip.clone()); + } + + Some(FilterConfig { + config: Arc::new(FilterConfigImpl { + denied_addresses_exact, + allowed_addresses_exact, + }), + }) } - - let mut denied_addresses_exact = HashSet::new(); - let mut allowed_addresses_exact = HashSet::new(); - - // Validate every ip in the set is a valid IPv4 address or IPv6 address. - for ip in &filter_config.allowed_addresses { - if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { - eprintln!("Error parsing ip in allowed_addresses: {ip}"); - return None; - } - allowed_addresses_exact.insert(ip.clone()); - } - for ip in &filter_config.denied_addresses { - if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { - eprintln!("Error parsing ip in denied_addresses: {ip}"); - return None; - } - denied_addresses_exact.insert(ip.clone()); - } - - Some(FilterConfig { - config: Arc::new(FilterConfigImpl { - denied_addresses_exact: denied_addresses_exact, - allowed_addresses_exact: allowed_addresses_exact, - }), - }) - } } impl HttpFilterConfig for FilterConfig { - /// This is called for each new HTTP filter. - fn new_http_filter(&mut self, _envoy: &mut EC) -> Box> { - Box::new(Filter { - filter_config: self.clone(), - }) - } + /// This is called for each new HTTP filter. + fn new_http_filter(&mut self, _envoy: &mut EC) -> Box> { + Box::new(Filter { + filter_config: self.clone(), + }) + } } /// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. /// /// This sets the request and response headers to the values specified in the filter config. pub struct Filter { - // The filter config have longer lifetime than the filter. - filter_config: FilterConfig, + // The filter config have longer lifetime than the filter. + filter_config: FilterConfig, } /// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. impl HttpFilter for Filter { - fn on_request_headers( - &mut self, - envoy_filter: &mut EHF, - _end_stream: bool, - ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { - let downstream_addr = - envoy_filter.get_attribute_string(abi::envoy_dynamic_module_type_attribute_id::SourceAddress); - let downstream_port = - envoy_filter.get_attribute_int(abi::envoy_dynamic_module_type_attribute_id::SourcePort); - - if downstream_addr.is_none() || downstream_port.is_none() { - envoy_filter.send_response( - 403, - vec![], - Some(b"No remote address and request is forbidden."), - ); - return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + let downstream_addr = envoy_filter + .get_attribute_string(abi::envoy_dynamic_module_type_attribute_id::SourceAddress); + let downstream_port = + envoy_filter.get_attribute_int(abi::envoy_dynamic_module_type_attribute_id::SourcePort); + + if downstream_addr.is_none() || downstream_port.is_none() { + envoy_filter.send_response( + 403, + vec![], + Some(b"No remote address and request is forbidden."), + ); + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + } + + let mut downstream_addr_str = String::new(); + + let address_buffer = downstream_addr.unwrap(); + let downstream_addr_slice = address_buffer.as_slice(); + + if downstream_port.is_none() { + // Covert the slice of downstream addr to string. + unsafe { + downstream_addr_str + .as_mut_vec() + .extend_from_slice(downstream_addr_slice); + } + } else { + // Strip the port from the downstream addr. + let downstream_addr_slice = &downstream_addr_slice + [0..downstream_addr_slice.len() - downstream_port.unwrap().to_string().len() - 1]; + + unsafe { + downstream_addr_str + .as_mut_vec() + .extend_from_slice(downstream_addr_slice); + } + } + + // Check if the downstream addr is in the allowed list. + if !self.filter_config.config.allowed_addresses_exact.is_empty() + && !self + .filter_config + .config + .allowed_addresses_exact + .contains(&downstream_addr_str) + { + envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + } + + // Check if the downstream addr is in the denied list. + if !self.filter_config.config.denied_addresses_exact.is_empty() + && self + .filter_config + .config + .denied_addresses_exact + .contains(&downstream_addr_str) + { + envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; + } + + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue } - - let mut downstream_addr_str = String::new(); - - let address_buffer = downstream_addr.unwrap(); - let downstream_addr_slice = address_buffer.as_slice(); - - if downstream_port.is_none() { - // Covert the slice of downstream addr to string. - unsafe { - downstream_addr_str - .as_mut_vec() - .extend_from_slice(downstream_addr_slice); - } - } else { - // Strip the port from the downstream addr. - let downstream_addr_slice = &downstream_addr_slice - [0..downstream_addr_slice.len() - downstream_port.unwrap().to_string().len() - 1]; - - unsafe { - downstream_addr_str - .as_mut_vec() - .extend_from_slice(downstream_addr_slice); - } - } - - // Check if the downstream addr is in the allowed list. - if !self.filter_config.config.allowed_addresses_exact.is_empty() { - if !self - .filter_config - .config - .allowed_addresses_exact - .contains(&downstream_addr_str) - { - envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); - return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; - } - } - - // Check if the downstream addr is in the denied list. - if !self.filter_config.config.denied_addresses_exact.is_empty() { - if self - .filter_config - .config - .denied_addresses_exact - .contains(&downstream_addr_str) - { - envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); - return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; - } - } - - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue - } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_new_filter_config_both_set() { - let filter_config = FilterConfig::new( - r#"{ + #[test] + fn test_new_filter_config_both_set() { + let filter_config = FilterConfig::new( + r#"{ "allowed_addresses": [ "127.0.0.1", "::1" @@ -190,182 +188,185 @@ mod tests { "192.168.1.1" ] }"#, - ); - assert!(filter_config.is_none()); // Only one of allowed_addresses and denied_addresses should be set. - } - - #[test] - fn test_new_filter_config_allowed_set() { - let filter_config = FilterConfig::new( - r#"{ + ); + assert!(filter_config.is_none()); // Only one of allowed_addresses and denied_addresses should be set. + } + + #[test] + fn test_new_filter_config_allowed_set() { + let filter_config = FilterConfig::new( + r#"{ "allowed_addresses": [ "127.0.0.1", "::1" ] }"#, - ); - assert!(filter_config.is_some()); - } - - #[test] - fn test_new_filter_config_denied_set() { - let filter_config = FilterConfig::new( - r#"{ + ); + assert!(filter_config.is_some()); + } + + #[test] + fn test_new_filter_config_denied_set() { + let filter_config = FilterConfig::new( + r#"{ "denied_addresses": [ "192.168.1.1" ] }"#, - ); - assert!(filter_config.is_some()); - } - - #[test] - fn test_new_filter_config_invalid_ip() { - let filter_config = FilterConfig::new( - r#"{ + ); + assert!(filter_config.is_some()); + } + + #[test] + fn test_new_filter_config_invalid_ip() { + let filter_config = FilterConfig::new( + r#"{ "allowed_addresses": [ "127.0.0.1", "invalid_ip" ] }"#, - ); - assert!(filter_config.is_none()); - } - - #[test] - fn test_filter_denied_because_no_address() { - let filter_config = FilterConfig::new( - r#"{ + ); + assert!(filter_config.is_none()); + } + + #[test] + fn test_filter_denied_because_no_address() { + let filter_config = FilterConfig::new( + r#"{ "denied_addresses": [ "192.168.1.1" ] }"#, - ); - assert!(filter_config.is_some()); - - let mut filter = Filter { - filter_config: filter_config.unwrap(), - }; - - let mut mock_envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); - - mock_envoy_filter - .expect_get_attribute_string() - .times(1) - .returning(|_| None); - mock_envoy_filter - .expect_get_attribute_int() - .times(1) - .returning(|_| None); - mock_envoy_filter - .expect_send_response() - .times(1) - .returning(|code, _, _| assert!(code == 403)); - - assert_eq!( - filter.on_request_headers(&mut mock_envoy_filter, true), - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration - ); - } - - #[test] - fn test_filter_with_allowed_list() { - let filter_config = FilterConfig::new( - r#"{ + ); + assert!(filter_config.is_some()); + + let mut filter = Filter { + filter_config: filter_config.unwrap(), + }; + + let mut mock_envoy_filter = + envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| None); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| None); + mock_envoy_filter + .expect_send_response() + .times(1) + .returning(|code, _, _| assert!(code == 403)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + ); + } + + #[test] + fn test_filter_with_allowed_list() { + let filter_config = FilterConfig::new( + r#"{ "allowed_addresses": [ "127.0.0.1", "::1" ] }"#, - ); - assert!(filter_config.is_some()); - - let mut filter = Filter { - filter_config: filter_config.unwrap(), - }; - - let mut mock_envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); - - mock_envoy_filter - .expect_get_attribute_string() - .times(1) - .returning(|_| Some(EnvoyBuffer::new("127.0.0.1:80"))); - mock_envoy_filter - .expect_get_attribute_int() - .times(1) - .returning(|_| Some(80)); - - assert_eq!( - filter.on_request_headers(&mut mock_envoy_filter, true), - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue - ); - - mock_envoy_filter - .expect_get_attribute_string() - .times(1) - .returning(|_| Some(EnvoyBuffer::new("192.168.1.1:80"))); - mock_envoy_filter - .expect_get_attribute_int() - .times(1) - .returning(|_| Some(80)); - mock_envoy_filter - .expect_send_response() - .times(1) - .returning(|code, _, _| assert!(code == 403)); - - assert_eq!( - filter.on_request_headers(&mut mock_envoy_filter, true), - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration - ); - } - - #[test] - fn test_filter_with_denied_list() { - let filter_config = FilterConfig::new( - r#"{ + ); + assert!(filter_config.is_some()); + + let mut filter = Filter { + filter_config: filter_config.unwrap(), + }; + + let mut mock_envoy_filter = + envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("127.0.0.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + ); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("192.168.1.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + mock_envoy_filter + .expect_send_response() + .times(1) + .returning(|code, _, _| assert!(code == 403)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + ); + } + + #[test] + fn test_filter_with_denied_list() { + let filter_config = FilterConfig::new( + r#"{ "denied_addresses": [ "192.168.1.1" ] }"#, - ); - assert!(filter_config.is_some()); - - let mut filter = Filter { - filter_config: filter_config.unwrap(), - }; - - let mut mock_envoy_filter = envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); - - mock_envoy_filter - .expect_get_attribute_string() - .times(1) - .returning(|_| Some(EnvoyBuffer::new("192.168.1.1:80"))); - mock_envoy_filter - .expect_get_attribute_int() - .times(1) - .returning(|_| Some(80)); - mock_envoy_filter - .expect_send_response() - .times(1) - .returning(|code, _, _| assert!(code == 403)); - - assert_eq!( - filter.on_request_headers(&mut mock_envoy_filter, true), - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration - ); - - mock_envoy_filter - .expect_get_attribute_string() - .times(1) - .returning(|_| Some(EnvoyBuffer::new("127.0.0.1:80"))); - mock_envoy_filter - .expect_get_attribute_int() - .times(1) - .returning(|_| Some(80)); - - assert_eq!( - filter.on_request_headers(&mut mock_envoy_filter, true), - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue - ); - } + ); + assert!(filter_config.is_some()); + + let mut filter = Filter { + filter_config: filter_config.unwrap(), + }; + + let mut mock_envoy_filter = + envoy_proxy_dynamic_modules_rust_sdk::MockEnvoyHttpFilter::new(); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("192.168.1.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + mock_envoy_filter + .expect_send_response() + .times(1) + .returning(|code, _, _| assert!(code == 403)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + ); + + mock_envoy_filter + .expect_get_attribute_string() + .times(1) + .returning(|_| Some(EnvoyBuffer::new("127.0.0.1:80"))); + mock_envoy_filter + .expect_get_attribute_int() + .times(1) + .returning(|_| Some(80)); + + assert_eq!( + filter.on_request_headers(&mut mock_envoy_filter, true), + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + ); + } } diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index f7abdfb..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,60 +0,0 @@ -# This config is copied from https://github.com/bitdriftlabs/shared-core/blob/4114708cafb80103092b7e585987ef275a136f87/rustfmt.toml - -# Rust Autoformatter Config -# ========================= -# -# Note that most of rustfmt's configurable settings are nightly-only, -# so we tend to run that. Typically we don't rely on unstable features, -# but in this case there's honestly not a lot to configure that _isn't_ -# behind the `unstable_features` gate, unfortunately. - -edition = "2021" -unstable_features = true - -# Try to use inline annotations before using this -- disabling autoformatting -# for a file makes it harder for everyone to share their work in that file and -# invites bikeshedding about how code in there should look. -ignore = [] - -# Features are broken up into Stable and Unstable features, listed in order of -# appearance in the Configuring Rustfmt document: -# https://github.com/rust-lang-nursery/rustfmt/blob/master/Configurations.md - -## Stable Features - -force_explicit_abi = true # Default -hard_tabs = false # Default -max_width = 100 -merge_derives = true # Default -newline_style = "Auto" # Default -remove_nested_parens = true # Default -reorder_imports = true # Default -reorder_modules = true # Default -tab_spaces = 2 -use_field_init_shorthand = false # Default -use_small_heuristics = "Default" # Default -use_try_shorthand = true - -## Features that were "unstable" when we configured them -blank_lines_lower_bound = 0 -blank_lines_upper_bound = 3 -combine_control_expr = false -comment_width = 100 -condense_wildcard_suffixes = true -enum_discrim_align_threshold = 20 -error_on_line_overflow = true -error_on_unformatted = false -format_code_in_doc_comments = true -format_macro_bodies = true -format_macro_matchers = true -format_strings = true -group_imports = "One" -imports_granularity = "Module" -imports_layout = "HorizontalVertical" -match_block_trailing_comma = true -normalize_comments = false -normalize_doc_attributes = true -skip_children = false -spaces_around_ranges = true -struct_field_align_threshold = 0 -wrap_comments = true From 6ed3db6b17f849d967680d3fccc061603d4f1b16 Mon Sep 17 00:00:00 2001 From: "wangbaiping(wbpcode)" Date: Wed, 16 Jul 2025 12:45:18 +0000 Subject: [PATCH 6/6] add integration test Signed-off-by: wangbaiping(wbpcode) --- Cargo.lock | 2 +- dynamic/rust/ip_restriction/Cargo.toml | 2 +- dynamic/rust/ip_restriction/README.md | 3 + dynamic/rust/ip_restriction/src/lib.rs | 103 ++++++++++++------ integration/integration.sh | 55 ++++++++++ .../ip_restriction/docker-compose.yaml | 18 +++ integration/ip_restriction/envoy.yaml | 61 +++++++++++ integration/ip_restriction/verify.py | 22 ++++ 8 files changed, 229 insertions(+), 37 deletions(-) create mode 100644 dynamic/rust/ip_restriction/README.md create mode 100644 integration/integration.sh create mode 100644 integration/ip_restriction/docker-compose.yaml create mode 100644 integration/ip_restriction/envoy.yaml create mode 100644 integration/ip_restriction/verify.py diff --git a/Cargo.lock b/Cargo.lock index c406802..9487cc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,7 @@ dependencies = [ [[package]] name = "envoy-proxy-dynamic-modules-rust-sdk" version = "0.1.0" -source = "git+https://github.com/envoyproxy/envoy?rev=73fe00fc139fd5053f4c4a5d66569cc254449896#73fe00fc139fd5053f4c4a5d66569cc254449896" +source = "git+https://github.com/envoyproxy/envoy?rev=4d2c32b3f3ec5cd1be5666819cc190ef2b0da8e3#4d2c32b3f3ec5cd1be5666819cc190ef2b0da8e3" dependencies = [ "bindgen", "mockall", diff --git a/dynamic/rust/ip_restriction/Cargo.toml b/dynamic/rust/ip_restriction/Cargo.toml index fd9c4f4..c2378a6 100644 --- a/dynamic/rust/ip_restriction/Cargo.toml +++ b/dynamic/rust/ip_restriction/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/envoyproxy/modules" [dependencies] # The SDK version must match the Envoy version due to the strict compatibility requirements. -envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "73fe00fc139fd5053f4c4a5d66569cc254449896" } +envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "4d2c32b3f3ec5cd1be5666819cc190ef2b0da8e3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/dynamic/rust/ip_restriction/README.md b/dynamic/rust/ip_restriction/README.md new file mode 100644 index 0000000..7841eec --- /dev/null +++ b/dynamic/rust/ip_restriction/README.md @@ -0,0 +1,3 @@ +# IP Restriction Dynamic Module + +This module provides IP-based access control for your application. It allows you to define allowlists or denylists of IP addresses, restricting access to specific endpoints or the entire service. diff --git a/dynamic/rust/ip_restriction/src/lib.rs b/dynamic/rust/ip_restriction/src/lib.rs index 183b6be..718727e 100644 --- a/dynamic/rust/ip_restriction/src/lib.rs +++ b/dynamic/rust/ip_restriction/src/lib.rs @@ -12,15 +12,15 @@ use std::sync::Arc; #[derive(Serialize, Deserialize, Debug)] pub struct RawFilterConfig { #[serde(default)] - denied_addresses: HashSet, + deny_addresses: HashSet, #[serde(default)] - allowed_addresses: HashSet, + allow_addresses: HashSet, } #[derive(Debug)] pub struct FilterConfigImpl { - denied_addresses_exact: HashSet, - allowed_addresses_exact: HashSet, + deny_addresses_exact: HashSet, + allow_addresses_exact: HashSet, } // This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] trait. @@ -32,10 +32,10 @@ pub struct FilterConfig { } impl FilterConfig { - /// This is the constructor for the [`FilterConfig`]. - /// - /// filter_config is the filter config from the Envoy config here: - /// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig + // This is the constructor for the [`FilterConfig`]. + // + // filter_config is the filter config from the Envoy config here: + // https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig pub fn new(filter_config: &str) -> Option { let filter_config: RawFilterConfig = match serde_json::from_str(filter_config) { Ok(cfg) => cfg, @@ -45,38 +45,38 @@ impl FilterConfig { } }; - // One and only one of denied_addresses and allowed_addresses should be set. - if filter_config.denied_addresses.is_empty() == filter_config.allowed_addresses.is_empty() { + // One and only one of deny_addresses and allow_addresses should be set. + if filter_config.deny_addresses.is_empty() == filter_config.allow_addresses.is_empty() { eprintln!( - "Error parsing filter config: one and only one of denied_addresses\ - and allowed_addresses should be set" + "Error parsing filter config: one and only one of deny_addresses\ + and allow_addresses should be set" ); return None; } - let mut denied_addresses_exact = HashSet::new(); - let mut allowed_addresses_exact = HashSet::new(); + let mut deny_addresses_exact = HashSet::new(); + let mut allow_addresses_exact = HashSet::new(); // Validate every ip in the set is a valid IPv4 address or IPv6 address. - for ip in &filter_config.allowed_addresses { + for ip in &filter_config.allow_addresses { if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { - eprintln!("Error parsing ip in allowed_addresses: {ip}"); + eprintln!("Error parsing ip in allow_addresses: {ip}"); return None; } - allowed_addresses_exact.insert(ip.clone()); + allow_addresses_exact.insert(ip.clone()); } - for ip in &filter_config.denied_addresses { + for ip in &filter_config.deny_addresses { if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { - eprintln!("Error parsing ip in denied_addresses: {ip}"); + eprintln!("Error parsing ip in deny_addresses: {ip}"); return None; } - denied_addresses_exact.insert(ip.clone()); + deny_addresses_exact.insert(ip.clone()); } Some(FilterConfig { config: Arc::new(FilterConfigImpl { - denied_addresses_exact, - allowed_addresses_exact, + deny_addresses_exact, + allow_addresses_exact, }), }) } @@ -145,11 +145,11 @@ impl HttpFilter for Filter { } // Check if the downstream addr is in the allowed list. - if !self.filter_config.config.allowed_addresses_exact.is_empty() + if !self.filter_config.config.allow_addresses_exact.is_empty() && !self .filter_config .config - .allowed_addresses_exact + .allow_addresses_exact .contains(&downstream_addr_str) { envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); @@ -157,11 +157,11 @@ impl HttpFilter for Filter { } // Check if the downstream addr is in the denied list. - if !self.filter_config.config.denied_addresses_exact.is_empty() + if !self.filter_config.config.deny_addresses_exact.is_empty() && self .filter_config .config - .denied_addresses_exact + .deny_addresses_exact .contains(&downstream_addr_str) { envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); @@ -172,6 +172,39 @@ impl HttpFilter for Filter { } } +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::ProgramInitFunction`]. +/// +/// This is called exactly once when the module is loaded. It can be used to +/// initialize global state as well as check the runtime environment to ensure that +/// the module is running in a supported environment. +/// +/// Returning `false` will cause Envoy to reject the config hence the +/// filter will not be loaded. +fn init() -> bool { + true +} + +// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::NewHttpFilterConfigFunction`]. +// +// This is the entrypoint every time a new HTTP filter is created via the DynamicModuleFilter config. +// TODO(wbpcode): rust SDK doesn't provide the mock of EnvoyHttpFilterConfig, +// so we can't test the new_http_filter_config_fn function. +#[allow(dead_code)] +fn new_http_filter_config_fn( + _envoy_filter_config: &mut EC, + filter_name: &str, + filter_config: &[u8], +) -> Option>> { + let filter_config = std::str::from_utf8(filter_config).unwrap(); + match filter_name { + "ip_restriction" => FilterConfig::new(filter_config) + .map(|config| Box::new(config) as Box>), + _ => panic!("Unknown filter name: {filter_name}"), + } +} + +declare_init_functions!(init, new_http_filter_config_fn); + #[cfg(test)] mod tests { use super::*; @@ -180,23 +213,23 @@ mod tests { fn test_new_filter_config_both_set() { let filter_config = FilterConfig::new( r#"{ - "allowed_addresses": [ + "allow_addresses": [ "127.0.0.1", "::1" ], - "denied_addresses": [ + "deny_addresses": [ "192.168.1.1" ] }"#, ); - assert!(filter_config.is_none()); // Only one of allowed_addresses and denied_addresses should be set. + assert!(filter_config.is_none()); // Only one of allow_addresses and deny_addresses should be set. } #[test] fn test_new_filter_config_allowed_set() { let filter_config = FilterConfig::new( r#"{ - "allowed_addresses": [ + "allow_addresses": [ "127.0.0.1", "::1" ] @@ -209,7 +242,7 @@ mod tests { fn test_new_filter_config_denied_set() { let filter_config = FilterConfig::new( r#"{ - "denied_addresses": [ + "deny_addresses": [ "192.168.1.1" ] }"#, @@ -221,7 +254,7 @@ mod tests { fn test_new_filter_config_invalid_ip() { let filter_config = FilterConfig::new( r#"{ - "allowed_addresses": [ + "allow_addresses": [ "127.0.0.1", "invalid_ip" ] @@ -234,7 +267,7 @@ mod tests { fn test_filter_denied_because_no_address() { let filter_config = FilterConfig::new( r#"{ - "denied_addresses": [ + "deny_addresses": [ "192.168.1.1" ] }"#, @@ -271,7 +304,7 @@ mod tests { fn test_filter_with_allowed_list() { let filter_config = FilterConfig::new( r#"{ - "allowed_addresses": [ + "allow_addresses": [ "127.0.0.1", "::1" ] @@ -323,7 +356,7 @@ mod tests { fn test_filter_with_denied_list() { let filter_config = FilterConfig::new( r#"{ - "denied_addresses": [ + "deny_addresses": [ "192.168.1.1" ] }"#, diff --git a/integration/integration.sh b/integration/integration.sh new file mode 100644 index 0000000..2f17115 --- /dev/null +++ b/integration/integration.sh @@ -0,0 +1,55 @@ +#!/bin/bash -e + + +CURRENT_SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +MODULE_NAME=$1 + +if [ ! -d "${CURRENT_SCRIPT_DIR}/${MODULE_NAME}" ]; then + echo "Module directory ${MODULE_NAME} does not exist." + exit 1 +fi + +cleanup () { + echo "Cleaning up module: ${MODULE_NAME}" + + local code="$?" + if [ $code -ne 0 ]; then + echo "Verification of module ${MODULE_NAME} failed." + docker compose -f docker-compose.yaml logs + fi + + down_args=(--remove-orphans) + if [[ -n "$DOCKER_RMI_CLEANUP" ]]; then + down_args+=(--rmi all) + fi + + docker compose -f docker-compose.yaml down "${down_args[@]}" -v + + popd || exit 1 + + exit $code +} + +verify () { + echo "Verifying module: ${MODULE_NAME}" + + pushd "${CURRENT_SCRIPT_DIR}/${MODULE_NAME}" || exit 1 + + docker compose -f docker-compose.yaml up -d --build || { + echo "Failed to start module ${MODULE_NAME}." + exit 1 + } + + sleep 5 # Wait for services to start + + python3 verify.py || { + echo "Verification script not found or failed for module ${MODULE_NAME}." + exit 1 + } + + echo "Module ${MODULE_NAME} verified successfully." +} + +trap cleanup EXIT + +verify diff --git a/integration/ip_restriction/docker-compose.yaml b/integration/ip_restriction/docker-compose.yaml new file mode 100644 index 0000000..f2e0f04 --- /dev/null +++ b/integration/ip_restriction/docker-compose.yaml @@ -0,0 +1,18 @@ +services: + proxy: + image: ${ENVOY_IMAGE:-envoyproxy/envoy-dev:latest} + ports: + - "10000:10000" + volumes: + - "./envoy.yaml:/etc/envoy.yaml" + - "../../target/debug:/target/debug" + entrypoint: + - /usr/local/bin/envoy + - -c + - /etc/envoy.yaml + - --concurrency + - "1" + environment: + - ENVOY_DYNAMIC_MODULES_SEARCH_PATH=/target/debug + web_service: + image: kennethreitz/httpbin diff --git a/integration/ip_restriction/envoy.yaml b/integration/ip_restriction/envoy.yaml new file mode 100644 index 0000000..e361e31 --- /dev/null +++ b/integration/ip_restriction/envoy.yaml @@ -0,0 +1,61 @@ +static_resources: + listeners: + - name: main + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + codec_type: AUTO + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: web_service + http_filters: + - name: dynamic_rust_ip_restriction + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_modules.v3.DynamicModuleFilter + dynamic_module_config: + name: ip_restriction + filter_name: ip_restriction + filter_config: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { + "deny_addresses": [ + "192.168.22.33" + ] + } + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: web_service + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: web_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: web_service + port_value: 80 +admin: + address: + socket_address: + address: 0.0.0.0 + port_value: 10001 diff --git a/integration/ip_restriction/verify.py b/integration/ip_restriction/verify.py new file mode 100644 index 0000000..df4d70c --- /dev/null +++ b/integration/ip_restriction/verify.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import http.client + +def verify_normal_request(): + conn = http.client.HTTPConnection("localhost", 10000) + conn.request("GET", "/status/200") + response = conn.getresponse() + return response.status == 200 + + +def verify_ip_restricted_request(): + conn = http.client.HTTPConnection("localhost", 10000) + conn.request("GET", "/status/200", headers={"X-Forwarded-For": "192.168.22.33"}) + response = conn.getresponse() + return response.status == 403 + + +if __name__ == "__main__": + assert verify_normal_request(), "Normal request failed" + assert verify_ip_restricted_request(), "IP restricted request did not return 403" + print("All verifications for IP Restriction Dynamic Module passed.")