diff --git a/integration/envoy.yaml b/integration/envoy.yaml index 3b0b407..1e351f5 100644 --- a/integration/envoy.yaml +++ b/integration/envoy.yaml @@ -43,6 +43,37 @@ static_resources: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + - address: + socket_address: + address: 0.0.0.0 + port_value: 1063 + 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 + route_config: + virtual_hosts: + - name: local_route + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: httpbin + http_filters: + - name: dynamic_modules/passthrough + typed_config: + # 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 + "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_modules.v3.DynamicModuleFilter + dynamic_module_config: + name: rust_module + filter_name: random_auth + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: httpbin connect_timeout: 5000s diff --git a/integration/main_test.go b/integration/main_test.go index 9aa6396..513c73d 100644 --- a/integration/main_test.go +++ b/integration/main_test.go @@ -65,28 +65,28 @@ func TestIntegration(t *testing.T) { // to pull the image. time.Sleep(5 * time.Second) - t.Run("health checking", func(t *testing.T) { - require.Eventually(t, func() bool { - req, err := http.NewRequest("GET", "http://localhost:1062/uuid", nil) - require.NoError(t, err) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Logf("Envoy not ready yet: %v", err) - return false - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - t.Logf("Envoy not ready yet: %v", err) - return false - } - t.Logf("response: status=%d body=%s", resp.StatusCode, string(body)) - return resp.StatusCode == 200 - }, 30*time.Second, 1*time.Second) - }) + t.Run("http_access_logger", func(t *testing.T) { + t.Run("health checking", func(t *testing.T) { + require.Eventually(t, func() bool { + req, err := http.NewRequest("GET", "http://localhost:1062/uuid", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Logf("Envoy not ready yet: %v", err) + return false + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Logf("Envoy not ready yet: %v", err) + return false + } + t.Logf("response: status=%d body=%s", resp.StatusCode, string(body)) + return resp.StatusCode == 200 + }, 30*time.Second, 1*time.Second) + }) - t.Run("check access log", func(t *testing.T) { require.Eventually(t, func() bool { // List files in the access log directory files, err := os.ReadDir(tmpdir) @@ -131,4 +131,33 @@ func TestIntegration(t *testing.T) { return found }, 30*time.Second, 1*time.Second) }) + + t.Run("http_random_auth", func(t *testing.T) { + got200 := false + got403 := false + require.Eventually(t, func() bool { + req, err := http.NewRequest("GET", "http://localhost:1063/uuid", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Logf("Envoy not ready yet: %v", err) + return false + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Logf("Envoy not ready yet: %v", err) + return false + } + t.Logf("response: status=%d body=%s", resp.StatusCode, string(body)) + if resp.StatusCode == 200 { + got200 = true + } + if resp.StatusCode == 403 { + got403 = true + } + return got200 && got403 + }, 30*time.Second, 200*time.Millisecond) + }) } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index bf4354b..810231f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -43,6 +43,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cexpr" version = "0.6.0" @@ -95,6 +101,7 @@ name = "envoy-proxy-dynamic-modules-rust-sdk-examples" version = "0.1.0" dependencies = [ "envoy-proxy-dynamic-modules-rust-sdk", + "rand", "serde", "serde_json", "tempfile", @@ -237,6 +244,15 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] + [[package]] name = "predicates" version = "3.1.3" @@ -291,6 +307,37 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy 0.8.17", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom", + "zerocopy 0.8.17", +] + [[package]] name = "regex" version = "1.11.1" @@ -510,3 +557,44 @@ checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ "bitflags", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a2363ed..f1a2262 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/envoyproxy/dynamic-modules-example" envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "4a113b5118003682833ba612202eb68628861ac6" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +rand = "0.9.0" [dev-dependencies] tempfile = "3.16.0" diff --git a/rust/src/http_random_auth.rs b/rust/src/http_random_auth.rs new file mode 100644 index 0000000..798f28c --- /dev/null +++ b/rust/src/http_random_auth.rs @@ -0,0 +1,47 @@ +use envoy_proxy_dynamic_modules_rust_sdk::*; +use rand::prelude::*; + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] trait. +/// +/// The trait corresponds to a Envoy filter chain configuration. +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 + pub fn new(_filter_config: &str) -> Self { + Self {} + } +} + +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 {}) + } +} + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. +/// +/// This is a passthrough filter that does nothing. +pub struct Filter {} + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. +/// +/// Default implementation of all methods is to return `Continue`. +impl HttpFilter for Filter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + let reject = rand::rng().random::(); + if reject { + envoy_filter.send_response(403, vec![], Some(b"Access 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 + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 1743ac1..58ad94f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,6 +2,7 @@ use envoy_proxy_dynamic_modules_rust_sdk::*; mod http_access_logger; mod http_passthrough; +mod http_random_auth; declare_init_functions!(init, new_http_filter_config_fn); @@ -34,6 +35,7 @@ fn new_http_filter_config_fn( "passthrough" => Some(Box::new(http_passthrough::FilterConfig::new(filter_config))), "access_logger" => http_access_logger::FilterConfig::new(filter_config) .map(|config| Box::new(config) as Box>), + "random_auth" => Some(Box::new(http_random_auth::FilterConfig::new(filter_config))), _ => panic!("Unknown filter name: {}", filter_name), } }