|
| 1 | +use envoy_proxy_dynamic_modules_rust_sdk::*; |
| 2 | +use serde::{Deserialize, Serialize}; |
| 3 | +use std::collections::HashSet; |
| 4 | +use std::net::Ipv4Addr; |
| 5 | +use std::net::Ipv6Addr; |
| 6 | +use std::str::FromStr; |
| 7 | +use std::sync::Arc; |
| 8 | + |
| 9 | +// The raw filter config that will be deserialized from the JSON configuration. |
| 10 | +// TODO(wbpcode): To support protobuf based API declaration in the future. |
| 11 | +// TODO(wbpcode): to support ip range in the future. |
| 12 | +#[derive(Serialize, Deserialize, Debug)] |
| 13 | +pub struct RawFilterConfig { |
| 14 | + denied_addresses: HashSet<String>, |
| 15 | + allowed_addresses: HashSet<String>, |
| 16 | +} |
| 17 | + |
| 18 | +#[derive(Debug)] |
| 19 | +pub struct FilterConfigImpl { |
| 20 | + denied_addresses_exact: HashSet<String>, |
| 21 | + allowed_addresses_exact: HashSet<String>, |
| 22 | +} |
| 23 | + |
| 24 | +// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] trait. |
| 25 | +// |
| 26 | +// The trait corresponds to a Envoy filter chain configuration. |
| 27 | +#[derive(Debug, Clone)] |
| 28 | +pub struct FilterConfig { |
| 29 | + config: Arc<FilterConfigImpl>, |
| 30 | +} |
| 31 | + |
| 32 | +impl FilterConfig { |
| 33 | + /// This is the constructor for the [`FilterConfig`]. |
| 34 | + /// |
| 35 | + /// filter_config is the filter config from the Envoy config here: |
| 36 | + /// 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 |
| 37 | + pub fn new(filter_config: &str) -> Option<Self> { |
| 38 | + let filter_config: RawFilterConfig = match serde_json::from_str(filter_config) { |
| 39 | + Ok(cfg) => cfg, |
| 40 | + Err(err) => { |
| 41 | + eprintln!("Error parsing filter config: {err}"); |
| 42 | + return None; |
| 43 | + }, |
| 44 | + }; |
| 45 | + |
| 46 | + // One and only one of denied_addresses and allowed_addresses should be set. |
| 47 | + if filter_config.denied_addresses.is_empty() != filter_config.allowed_addresses.is_empty() { |
| 48 | + eprintln!( |
| 49 | + "Error parsing filter config: one and only one of denied_addresses\ |
| 50 | + and allowed_addresses should be set" |
| 51 | + ); |
| 52 | + return None; |
| 53 | + } |
| 54 | + |
| 55 | + let mut denied_addresses_exact = HashSet::new(); |
| 56 | + let mut allowed_addresses_exact = HashSet::new(); |
| 57 | + |
| 58 | + // Validate every ip in the set is a valid IPv4 address or IPv6 address. |
| 59 | + for ip in &filter_config.allowed_addresses { |
| 60 | + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { |
| 61 | + eprintln!("Error parsing ip in allowed_addresses: {ip}"); |
| 62 | + return None; |
| 63 | + } |
| 64 | + allowed_addresses_exact.insert(ip.clone()); |
| 65 | + } |
| 66 | + for ip in &filter_config.denied_addresses { |
| 67 | + if Ipv4Addr::from_str(ip).is_err() && Ipv6Addr::from_str(ip).is_err() { |
| 68 | + eprintln!("Error parsing ip in denied_addresses: {ip}"); |
| 69 | + return None; |
| 70 | + } |
| 71 | + denied_addresses_exact.insert(ip.clone()); |
| 72 | + } |
| 73 | + |
| 74 | + Some(FilterConfig { |
| 75 | + config: Arc::new(FilterConfigImpl { |
| 76 | + denied_addresses_exact: denied_addresses_exact, |
| 77 | + allowed_addresses_exact: allowed_addresses_exact, |
| 78 | + }), |
| 79 | + }) |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +impl<EC: EnvoyHttpFilterConfig, EHF: EnvoyHttpFilter> HttpFilterConfig<EC, EHF> for FilterConfig { |
| 84 | + /// This is called for each new HTTP filter. |
| 85 | + fn new_http_filter(&mut self, _envoy: &mut EC) -> Box<dyn HttpFilter<EHF>> { |
| 86 | + Box::new(Filter { |
| 87 | + filter_config: self.clone(), |
| 88 | + }) |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. |
| 93 | +/// |
| 94 | +/// This sets the request and response headers to the values specified in the filter config. |
| 95 | +pub struct Filter { |
| 96 | + // The filter config have longer lifetime than the filter. |
| 97 | + filter_config: FilterConfig, |
| 98 | +} |
| 99 | + |
| 100 | +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] trait. |
| 101 | +impl<EHF: EnvoyHttpFilter> HttpFilter<EHF> for Filter { |
| 102 | + fn on_request_headers( |
| 103 | + &mut self, |
| 104 | + envoy_filter: &mut EHF, |
| 105 | + _end_stream: bool, |
| 106 | + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { |
| 107 | + let downstream_addr = |
| 108 | + envoy_filter.get_attribute_string(abi::envoy_dynamic_module_type_attribute_id::SourceAddress); |
| 109 | + let downstream_port = |
| 110 | + envoy_filter.get_attribute_int(abi::envoy_dynamic_module_type_attribute_id::SourcePort); |
| 111 | + |
| 112 | + if downstream_addr.is_none() || downstream_port.is_none() { |
| 113 | + envoy_filter.send_response( |
| 114 | + 403, |
| 115 | + vec![], |
| 116 | + Some(b"No remote address and request is forbidden."), |
| 117 | + ); |
| 118 | + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; |
| 119 | + } |
| 120 | + |
| 121 | + let mut downstream_addr_str = String::new(); |
| 122 | + let address_buffer = downstream_addr.unwrap(); |
| 123 | + let downstream_addr_slice = address_buffer.as_slice(); |
| 124 | + |
| 125 | + if downstream_port.is_none() { |
| 126 | + // Covert the slice of downstream addr to string. |
| 127 | + unsafe { |
| 128 | + downstream_addr_str |
| 129 | + .as_mut_vec() |
| 130 | + .extend_from_slice(downstream_addr_slice); |
| 131 | + } |
| 132 | + } else { |
| 133 | + // Strip the port from the downstream addr. |
| 134 | + let downstream_addr_slice = &downstream_addr_slice |
| 135 | + [0..downstream_addr_slice.len() - downstream_port.unwrap().to_string().len() - 1]; |
| 136 | + |
| 137 | + unsafe { |
| 138 | + downstream_addr_str |
| 139 | + .as_mut_vec() |
| 140 | + .extend_from_slice(downstream_addr_slice); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + // Check if the downstream addr is in the allowed list. |
| 145 | + if !self.filter_config.config.allowed_addresses_exact.is_empty() { |
| 146 | + if !self |
| 147 | + .filter_config |
| 148 | + .config |
| 149 | + .allowed_addresses_exact |
| 150 | + .contains(&downstream_addr_str) |
| 151 | + { |
| 152 | + envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); |
| 153 | + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + // Check if the downstream addr is in the denied list. |
| 158 | + if !self.filter_config.config.denied_addresses_exact.is_empty() { |
| 159 | + if self |
| 160 | + .filter_config |
| 161 | + .config |
| 162 | + .denied_addresses_exact |
| 163 | + .contains(&downstream_addr_str) |
| 164 | + { |
| 165 | + envoy_filter.send_response(403, vec![], Some(b"Request is forbidden.")); |
| 166 | + return abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration; |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +#[cfg(test)] |
| 175 | +mod tests { |
| 176 | + use super::*; |
| 177 | +} |
0 commit comments