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/.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.lock b/Cargo.lock new file mode 100644 index 0000000..9487cc0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,417 @@ +# 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-ip-restriction" +version = "0.0.1" +dependencies = [ + "envoy-proxy-dynamic-modules-rust-sdk", + "serde", + "serde_json", +] + +[[package]] +name = "envoy-proxy-dynamic-modules-rust-sdk" +version = "0.1.0" +source = "git+https://github.com/envoyproxy/envoy?rev=4d2c32b3f3ec5cd1be5666819cc190ef2b0da8e3#4d2c32b3f3ec5cd1be5666819cc190ef2b0da8e3" +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 = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[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 = "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" +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/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..165bc8c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["dynamic/rust/ip_restriction"] diff --git a/dynamic/rust/ip_restriction/Cargo.lock b/dynamic/rust/ip_restriction/Cargo.lock new file mode 100644 index 0000000..7ce2f3b --- /dev/null +++ b/dynamic/rust/ip_restriction/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/ip_restriction/Cargo.toml b/dynamic/rust/ip_restriction/Cargo.toml new file mode 100644 index 0000000..c2378a6 --- /dev/null +++ b/dynamic/rust/ip_restriction/Cargo.toml @@ -0,0 +1,18 @@ + +[package] +name = "envoy-proxy-dynamic-modules-rust-ip-restriction" +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 = "4d2c32b3f3ec5cd1be5666819cc190ef2b0da8e3" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[lib] +name = "ip_restriction" +path = "src/lib.rs" +crate-type = ["cdylib"] 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 new file mode 100644 index 0000000..718727e --- /dev/null +++ b/dynamic/rust/ip_restriction/src/lib.rs @@ -0,0 +1,405 @@ +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 { + #[serde(default)] + deny_addresses: HashSet, + #[serde(default)] + allow_addresses: HashSet, +} + +#[derive(Debug)] +pub struct FilterConfigImpl { + deny_addresses_exact: HashSet, + allow_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, // 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 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 deny_addresses\ + and allow_addresses should be set" + ); + return None; + } + + 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.allow_addresses { + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { + eprintln!("Error parsing ip in allow_addresses: {ip}"); + return None; + } + allow_addresses_exact.insert(ip.clone()); + } + 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 deny_addresses: {ip}"); + return None; + } + deny_addresses_exact.insert(ip.clone()); + } + + Some(FilterConfig { + config: Arc::new(FilterConfigImpl { + deny_addresses_exact, + allow_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.allow_addresses_exact.is_empty() + && !self + .filter_config + .config + .allow_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.deny_addresses_exact.is_empty() + && self + .filter_config + .config + .deny_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 + } +} + +/// 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::*; + + #[test] + fn test_new_filter_config_both_set() { + let filter_config = FilterConfig::new( + r#"{ + "allow_addresses": [ + "127.0.0.1", + "::1" + ], + "deny_addresses": [ + "192.168.1.1" + ] + }"#, + ); + 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#"{ + "allow_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#"{ + "deny_addresses": [ + "192.168.1.1" + ] + }"#, + ); + assert!(filter_config.is_some()); + } + + #[test] + fn test_new_filter_config_invalid_ip() { + let filter_config = FilterConfig::new( + r#"{ + "allow_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#"{ + "deny_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#"{ + "allow_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#"{ + "deny_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 + ); + } +} 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.")