From 75c82691fcaee2fec9c5bc40f9630854b61468b9 Mon Sep 17 00:00:00 2001 From: ParrotXray Date: Sat, 23 May 2026 05:38:55 +0000 Subject: [PATCH] feat: Add egress eBPF access control and fix Suricata HTTP port detection --- CLAUDE.md | 18 +- common/src/define/program_array.rs | 5 +- config.toml | 2 +- egress-ebpf/src/action/access_control.rs | 110 ++++++++ egress-ebpf/src/action/mod.rs | 1 + egress-ebpf/src/main.rs | 47 +++- mantis/src/core/ebpf/access_control.rs | 275 ++++++++++++++----- mantis/src/core/ebpf/mod.rs | 2 +- mantis/src/core/infrastructure/app_db.rs | 4 - mantis/src/core/system.rs | 19 +- mantis/src/detection/suricata/engine.rs | 6 +- mantis/src/model/config.rs | 2 +- mantis/src/model/log/system.rs | 2 + mantis/src/web/api/control/access_control.rs | 30 +- 14 files changed, 421 insertions(+), 102 deletions(-) create mode 100644 egress-ebpf/src/action/access_control.rs diff --git a/CLAUDE.md b/CLAUDE.md index 69c74c3..819a419 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -161,14 +161,18 @@ Default admin: username=`admin`, password=`admin` (seeded on first boot). | Method | Path | Body | Description | |--------|------|------|-------------| -| GET | `/ipv4/{direction}/{list_type}` | — | Get IPv4 list | -| PUT | `/ipv4/{direction}/{list_type}` | `SocketAddrV4` JSON | Add entry | -| DELETE | `/ipv4/{direction}/{list_type}` | `SocketAddrV4` JSON | Remove entry | -| GET | `/ipv6/{direction}/{list_type}` | — | Get IPv6 list | -| PUT | `/ipv6/{direction}/{list_type}` | `SocketAddrV6` JSON | Add entry | -| DELETE | `/ipv6/{direction}/{list_type}` | `SocketAddrV6` JSON | Remove entry | +| GET | `/{nic}/ipv4/{flow}/{list_type}` | — | Get IPv4 list | +| PUT | `/{nic}/ipv4/{flow}/{list_type}` | `SocketAddrV4` JSON | Add entry | +| DELETE | `/{nic}/ipv4/{flow}/{list_type}` | `SocketAddrV4` JSON | Remove entry | +| GET | `/{nic}/ipv6/{flow}/{list_type}` | — | Get IPv6 list | +| PUT | `/{nic}/ipv6/{flow}/{list_type}` | `SocketAddrV6` JSON | Add entry | +| DELETE | `/{nic}/ipv6/{flow}/{list_type}` | `SocketAddrV6` JSON | Remove entry | -`direction`: `ingress` | `egress` — `list_type`: `whitelist` | `blacklist` +`nic`: `ingress` | `egress` — `flow`: `source` | `destination` — `list_type`: `whitelist` | `blacklist` + +Ingress access control targets the ingress eBPF (inbound packets from external network). +Egress access control targets the egress eBPF (outbound packets from internal network). +`source` matches the packet's src IP/port; `destination` matches dst IP/port. ### eBPF Service Control — `/ebpf/service` diff --git a/common/src/define/program_array.rs b/common/src/define/program_array.rs index f2b3bb1..e650988 100644 --- a/common/src/define/program_array.rs +++ b/common/src/define/program_array.rs @@ -6,6 +6,7 @@ pub mod ingress { } pub mod egress { - pub const STATISTICS: u32 = 0; - pub const TRANSMISSION: u32 = 1; + pub const ACCESS_CONTROL: u32 = 0; + pub const STATISTICS: u32 = 1; + pub const TRANSMISSION: u32 = 2; } diff --git a/config.toml b/config.toml index 6f63005..20e2fd6 100644 --- a/config.toml +++ b/config.toml @@ -49,7 +49,7 @@ default_admin_password = "admin" # Suricata rule engine. Remove this entire section to disable. [Config.suricata] -home_net = "140.130.34.0/24" +home_net = ["140.130.34.0/24"] worker_cpu_set = [4, 6] management_cpu = 0 af_packet_threads = "auto" diff --git a/egress-ebpf/src/action/access_control.rs b/egress-ebpf/src/action/access_control.rs new file mode 100644 index 0000000..f959649 --- /dev/null +++ b/egress-ebpf/src/action/access_control.rs @@ -0,0 +1,110 @@ +use aya_ebpf::macros::map; +use aya_ebpf::maps::HashMap; +use common::define::setting::{MAX_RULES, MAX_RULES_PORT}; +use common::model::event::{IPv4Event, IPv6Event}; +use common::model::ip_address::{IPv4, IPv6, Port}; + +#[map] +static EGRESS_IPV4_SRC_WHITELIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV6_SRC_WHITELIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV4_DST_WHITELIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV6_DST_WHITELIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV4_SRC_BLACKLIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV6_SRC_BLACKLIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV4_DST_BLACKLIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); +#[map] +static EGRESS_IPV6_DST_BLACKLIST: HashMap = + HashMap::with_max_entries(MAX_RULES as u32, 0); + +pub fn ipv4_is_whitelisted(event: &IPv4Event) -> bool { + unsafe { + if let Some(ports) = EGRESS_IPV4_SRC_WHITELIST.get(&event.src_ip) { + if is_port_exist(ports, event.src_port) { + return true; + } + } + if let Some(ports) = EGRESS_IPV4_DST_WHITELIST.get(&event.dst_ip) { + if is_port_exist(ports, event.dst_port) { + return true; + } + } + } + false +} + +pub fn ipv6_is_whitelisted(event: &IPv6Event) -> bool { + unsafe { + if let Some(ports) = EGRESS_IPV6_SRC_WHITELIST.get(&event.src_ip) { + if is_port_exist(ports, event.src_port) { + return true; + } + } + if let Some(ports) = EGRESS_IPV6_DST_WHITELIST.get(&event.dst_ip) { + if is_port_exist(ports, event.dst_port) { + return true; + } + } + } + false +} + +pub fn ipv4_is_blacklisted(event: &IPv4Event) -> bool { + unsafe { + if let Some(ports) = EGRESS_IPV4_SRC_BLACKLIST.get(&event.src_ip) { + if is_port_exist(ports, event.src_port) { + return true; + } + } + if let Some(ports) = EGRESS_IPV4_DST_BLACKLIST.get(&event.dst_ip) { + if is_port_exist(ports, event.dst_port) { + return true; + } + } + } + false +} + +pub fn ipv6_is_blacklisted(event: &IPv6Event) -> bool { + unsafe { + if let Some(ports) = EGRESS_IPV6_SRC_BLACKLIST.get(&event.src_ip) { + if is_port_exist(ports, event.src_port) { + return true; + } + } + if let Some(ports) = EGRESS_IPV6_DST_BLACKLIST.get(&event.dst_ip) { + if is_port_exist(ports, event.dst_port) { + return true; + } + } + } + false +} + +#[inline(always)] +fn is_port_exist(ports: &[Port; MAX_RULES_PORT], target_port: Port) -> bool { + if ports.get(0) == Some(&0) { + return true; + } + for &port in ports.iter() { + if port == 0 { + break; + } + if port == target_port { + return true; + } + } + false +} diff --git a/egress-ebpf/src/action/mod.rs b/egress-ebpf/src/action/mod.rs index 3449ec7..314c443 100644 --- a/egress-ebpf/src/action/mod.rs +++ b/egress-ebpf/src/action/mod.rs @@ -1 +1,2 @@ +pub mod access_control; pub mod statistics; diff --git a/egress-ebpf/src/main.rs b/egress-ebpf/src/main.rs index a2422f3..bd88cac 100644 --- a/egress-ebpf/src/main.rs +++ b/egress-ebpf/src/main.rs @@ -2,7 +2,7 @@ #![no_main] mod action; -use action::statistics; +use action::{access_control, statistics}; use aya_ebpf::bindings::xdp_action; use aya_ebpf::macros::{map, xdp}; use aya_ebpf::maps::{PerCpuArray, ProgramArray, XskMap}; @@ -33,7 +33,50 @@ unsafe fn packet_intake(ctx: XdpContext) -> Result { let end = ctx.data_end(); let ptr = PARSED_PACKET.get_ptr_mut(0).ok_or(())?; parsing::parse_packet(start, end, ptr)?; - let _ = PROGRAM_ARRAY.tail_call(&ctx, STATISTICS); + let _ = PROGRAM_ARRAY.tail_call(&ctx, ACCESS_CONTROL); + Err(()) + } +} + +#[xdp] +pub fn access_control(ctx: XdpContext) -> u32 { + unsafe { + match try_access_control(&ctx) { + Ok(action) => action, + Err(_) => { + let _ = PROGRAM_ARRAY.tail_call(&ctx, TRANSMISSION); + xdp_action::XDP_PASS + } + } + } +} + +#[inline(always)] +unsafe fn try_access_control(ctx: &XdpContext) -> Result { + unsafe { + let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?; + let parsed_packet = &*ptr; + match parsed_packet { + Event::IPv4(event) => { + if access_control::ipv4_is_whitelisted(event) { + let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS); + return Err(()); + } + if access_control::ipv4_is_blacklisted(event) { + return Ok(xdp_action::XDP_DROP); + } + } + Event::IPv6(event) => { + if access_control::ipv6_is_whitelisted(event) { + let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS); + return Err(()); + } + if access_control::ipv6_is_blacklisted(event) { + return Ok(xdp_action::XDP_DROP); + } + } + } + let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS); Err(()) } } diff --git a/mantis/src/core/ebpf/access_control.rs b/mantis/src/core/ebpf/access_control.rs index 38b0206..10b255b 100644 --- a/mantis/src/core/ebpf/access_control.rs +++ b/mantis/src/core/ebpf/access_control.rs @@ -7,7 +7,7 @@ use common::define::setting::MAX_RULES_PORT; use common::model::ip_address::{IPv4, IPv6, Port}; use tokio::sync::RwLock; -use crate::model::direction::FlowDirection; +use crate::model::direction::{Direction, FlowDirection}; use crate::model::error::Error; use crate::model::error::ebpf::EbpfError; use crate::model::ip_address::NativeConvert; @@ -15,117 +15,266 @@ use crate::model::list_type::ListType; use crate::utils::ip_address::convert_ports_to_vec; pub struct AccessControl { - ipv4_src_whitelist: RwLock>, - ipv4_src_blacklist: RwLock>, - ipv4_dst_whitelist: RwLock>, - ipv4_dst_blacklist: RwLock>, - ipv6_src_whitelist: RwLock>, - ipv6_src_blacklist: RwLock>, - ipv6_dst_whitelist: RwLock>, - ipv6_dst_blacklist: RwLock>, + ingress_ipv4_src_whitelist: RwLock>, + ingress_ipv4_src_blacklist: RwLock>, + ingress_ipv4_dst_whitelist: RwLock>, + ingress_ipv4_dst_blacklist: RwLock>, + ingress_ipv6_src_whitelist: RwLock>, + ingress_ipv6_src_blacklist: RwLock>, + ingress_ipv6_dst_whitelist: RwLock>, + ingress_ipv6_dst_blacklist: RwLock>, + egress_ipv4_src_whitelist: RwLock>, + egress_ipv4_src_blacklist: RwLock>, + egress_ipv4_dst_whitelist: RwLock>, + egress_ipv4_dst_blacklist: RwLock>, + egress_ipv6_src_whitelist: RwLock>, + egress_ipv6_src_blacklist: RwLock>, + egress_ipv6_dst_whitelist: RwLock>, + egress_ipv6_dst_blacklist: RwLock>, } impl AccessControl { - pub fn new(ebpf: &mut Ebpf) -> Result { - let access_control = Self { - ipv4_src_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV4_SRC_WHITELIST")?), - ipv4_src_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV4_SRC_BLACKLIST")?), - ipv4_dst_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV4_DST_WHITELIST")?), - ipv4_dst_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV4_DST_BLACKLIST")?), - ipv6_src_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV6_SRC_WHITELIST")?), - ipv6_src_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV6_SRC_BLACKLIST")?), - ipv6_dst_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV6_DST_WHITELIST")?), - ipv6_dst_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV6_DST_BLACKLIST")?), - }; - Ok(access_control) + pub fn new(ingress_ebpf: &mut Ebpf, egress_ebpf: &mut Ebpf) -> Result { + Ok(Self { + ingress_ipv4_src_whitelist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV4_SRC_WHITELIST")?), + ingress_ipv4_src_blacklist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV4_SRC_BLACKLIST")?), + ingress_ipv4_dst_whitelist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV4_DST_WHITELIST")?), + ingress_ipv4_dst_blacklist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV4_DST_BLACKLIST")?), + ingress_ipv6_src_whitelist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV6_SRC_WHITELIST")?), + ingress_ipv6_src_blacklist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV6_SRC_BLACKLIST")?), + ingress_ipv6_dst_whitelist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV6_DST_WHITELIST")?), + ingress_ipv6_dst_blacklist: RwLock::new(MapWrapper::new(ingress_ebpf, "IPV6_DST_BLACKLIST")?), + egress_ipv4_src_whitelist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV4_SRC_WHITELIST")?), + egress_ipv4_src_blacklist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV4_SRC_BLACKLIST")?), + egress_ipv4_dst_whitelist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV4_DST_WHITELIST")?), + egress_ipv4_dst_blacklist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV4_DST_BLACKLIST")?), + egress_ipv6_src_whitelist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV6_SRC_WHITELIST")?), + egress_ipv6_src_blacklist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV6_SRC_BLACKLIST")?), + egress_ipv6_dst_whitelist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV6_DST_WHITELIST")?), + egress_ipv6_dst_blacklist: RwLock::new(MapWrapper::new(egress_ebpf, "EGRESS_IPV6_DST_BLACKLIST")?), + }) } - pub async fn get_ipv4_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> { - let map_wrapper = match (direction, list_type) { - (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.read().await, - (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.read().await, - (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.read().await, - (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.read().await, + pub async fn get_ipv4_list( + &self, + nic: Direction, + flow: FlowDirection, + list_type: ListType, + ) -> HashMap> { + let guard = match (nic, flow, list_type) { + (Direction::Ingress, FlowDirection::Source, ListType::White) => { + self.ingress_ipv4_src_whitelist.read().await + } + (Direction::Ingress, FlowDirection::Source, ListType::Black) => { + self.ingress_ipv4_src_blacklist.read().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::White) => { + self.ingress_ipv4_dst_whitelist.read().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::Black) => { + self.ingress_ipv4_dst_blacklist.read().await + } + (Direction::Egress, FlowDirection::Source, ListType::White) => { + self.egress_ipv4_src_whitelist.read().await + } + (Direction::Egress, FlowDirection::Source, ListType::Black) => { + self.egress_ipv4_src_blacklist.read().await + } + (Direction::Egress, FlowDirection::Destination, ListType::White) => { + self.egress_ipv4_dst_whitelist.read().await + } + (Direction::Egress, FlowDirection::Destination, ListType::Black) => { + self.egress_ipv4_dst_blacklist.read().await + } }; - map_wrapper.get_list() + guard.get_list() } - pub async fn get_ipv6_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> { - let map_wrapper = match (direction, list_type) { - (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.read().await, - (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.read().await, - (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.read().await, - (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.read().await, + pub async fn get_ipv6_list( + &self, + nic: Direction, + flow: FlowDirection, + list_type: ListType, + ) -> HashMap> { + let guard = match (nic, flow, list_type) { + (Direction::Ingress, FlowDirection::Source, ListType::White) => { + self.ingress_ipv6_src_whitelist.read().await + } + (Direction::Ingress, FlowDirection::Source, ListType::Black) => { + self.ingress_ipv6_src_blacklist.read().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::White) => { + self.ingress_ipv6_dst_whitelist.read().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::Black) => { + self.ingress_ipv6_dst_blacklist.read().await + } + (Direction::Egress, FlowDirection::Source, ListType::White) => { + self.egress_ipv6_src_whitelist.read().await + } + (Direction::Egress, FlowDirection::Source, ListType::Black) => { + self.egress_ipv6_src_blacklist.read().await + } + (Direction::Egress, FlowDirection::Destination, ListType::White) => { + self.egress_ipv6_dst_whitelist.read().await + } + (Direction::Egress, FlowDirection::Destination, ListType::Black) => { + self.egress_ipv6_dst_blacklist.read().await + } }; - map_wrapper.get_list() + guard.get_list() } pub async fn add_ipv4_list( &self, - direction: FlowDirection, + nic: Direction, + flow: FlowDirection, list_type: ListType, address: SocketAddrV4, ) -> Result<(), Error> { let ip: u32 = (*address.ip()).to_bits().to_be(); let port = address.port(); - let mut map_wrapper = match (direction, list_type) { - (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write().await, - (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write().await, - (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write().await, - (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write().await, + let mut guard = match (nic, flow, list_type) { + (Direction::Ingress, FlowDirection::Source, ListType::White) => { + self.ingress_ipv4_src_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Source, ListType::Black) => { + self.ingress_ipv4_src_blacklist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::White) => { + self.ingress_ipv4_dst_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::Black) => { + self.ingress_ipv4_dst_blacklist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::White) => { + self.egress_ipv4_src_whitelist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::Black) => { + self.egress_ipv4_src_blacklist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::White) => { + self.egress_ipv4_dst_whitelist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::Black) => { + self.egress_ipv4_dst_blacklist.write().await + } }; - map_wrapper.add(ip, port) + guard.add(ip, port) } pub async fn add_ipv6_list( &self, - direction: FlowDirection, + nic: Direction, + flow: FlowDirection, list_type: ListType, address: SocketAddrV6, ) -> Result<(), Error> { let ip: u128 = (*address.ip()).to_bits().to_be(); let port = address.port(); - let mut map_wrapper = match (direction, list_type) { - (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write().await, - (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write().await, - (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write().await, - (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write().await, + let mut guard = match (nic, flow, list_type) { + (Direction::Ingress, FlowDirection::Source, ListType::White) => { + self.ingress_ipv6_src_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Source, ListType::Black) => { + self.ingress_ipv6_src_blacklist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::White) => { + self.ingress_ipv6_dst_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::Black) => { + self.ingress_ipv6_dst_blacklist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::White) => { + self.egress_ipv6_src_whitelist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::Black) => { + self.egress_ipv6_src_blacklist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::White) => { + self.egress_ipv6_dst_whitelist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::Black) => { + self.egress_ipv6_dst_blacklist.write().await + } }; - map_wrapper.add(ip, port) + guard.add(ip, port) } pub async fn remove_ipv4_list( &self, - direction: FlowDirection, + nic: Direction, + flow: FlowDirection, list_type: ListType, address: SocketAddrV4, ) -> Result<(), Error> { let ip: u32 = (*address.ip()).to_bits().to_be(); let port = address.port(); - let mut map_wrapper = match (direction, list_type) { - (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write().await, - (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write().await, - (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write().await, - (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write().await, + let mut guard = match (nic, flow, list_type) { + (Direction::Ingress, FlowDirection::Source, ListType::White) => { + self.ingress_ipv4_src_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Source, ListType::Black) => { + self.ingress_ipv4_src_blacklist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::White) => { + self.ingress_ipv4_dst_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::Black) => { + self.ingress_ipv4_dst_blacklist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::White) => { + self.egress_ipv4_src_whitelist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::Black) => { + self.egress_ipv4_src_blacklist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::White) => { + self.egress_ipv4_dst_whitelist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::Black) => { + self.egress_ipv4_dst_blacklist.write().await + } }; - map_wrapper.remove(ip, port) + guard.remove(ip, port) } pub async fn remove_ipv6_list( &self, - direction: FlowDirection, + nic: Direction, + flow: FlowDirection, list_type: ListType, address: SocketAddrV6, ) -> Result<(), Error> { let ip: u128 = (*address.ip()).to_bits().to_be(); let port = address.port(); - let mut map_wrapper = match (direction, list_type) { - (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write().await, - (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write().await, - (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write().await, - (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write().await, + let mut guard = match (nic, flow, list_type) { + (Direction::Ingress, FlowDirection::Source, ListType::White) => { + self.ingress_ipv6_src_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Source, ListType::Black) => { + self.ingress_ipv6_src_blacklist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::White) => { + self.ingress_ipv6_dst_whitelist.write().await + } + (Direction::Ingress, FlowDirection::Destination, ListType::Black) => { + self.ingress_ipv6_dst_blacklist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::White) => { + self.egress_ipv6_src_whitelist.write().await + } + (Direction::Egress, FlowDirection::Source, ListType::Black) => { + self.egress_ipv6_src_blacklist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::White) => { + self.egress_ipv6_dst_whitelist.write().await + } + (Direction::Egress, FlowDirection::Destination, ListType::Black) => { + self.egress_ipv6_dst_blacklist.write().await + } }; - map_wrapper.remove(ip, port) + guard.remove(ip, port) } } diff --git a/mantis/src/core/ebpf/mod.rs b/mantis/src/core/ebpf/mod.rs index b69e43e..f45d630 100644 --- a/mantis/src/core/ebpf/mod.rs +++ b/mantis/src/core/ebpf/mod.rs @@ -31,7 +31,7 @@ pub struct EbpfServices { impl EbpfServices { pub fn new(app_config: Arc, ingress_ebpf: &mut Ebpf, egress_ebpf: &mut Ebpf) -> Result { let xsk_manager = XskManager::new(app_config.clone(), ingress_ebpf, egress_ebpf)?; - let access_control = AccessControl::new(ingress_ebpf)?; + let access_control = AccessControl::new(ingress_ebpf, egress_ebpf)?; let service = Service::new(ingress_ebpf)?; let statistics = Statistics::new(app_config.clone(), ingress_ebpf, egress_ebpf)?; let ebpf_services = Self { diff --git a/mantis/src/core/infrastructure/app_db.rs b/mantis/src/core/infrastructure/app_db.rs index 41dedb1..7501d7d 100644 --- a/mantis/src/core/infrastructure/app_db.rs +++ b/mantis/src/core/infrastructure/app_db.rs @@ -25,10 +25,6 @@ impl AppDB { pub fn open(path: impl AsRef, key: &str) -> Result { let path = path.as_ref(); - if let Some(parent) = Path::new(path).parent() { - std::fs::create_dir_all(parent).map_err(|e| AuthError::DBError { msg: e.to_string() })?; - } - let conn = Connection::open(path).map_err(|e| AuthError::DBError { msg: e.to_string() })?; // Must be the first statement on the connection to unlock the encrypted DB. diff --git a/mantis/src/core/system.rs b/mantis/src/core/system.rs index b72a26c..b9386a1 100644 --- a/mantis/src/core/system.rs +++ b/mantis/src/core/system.rs @@ -40,6 +40,10 @@ pub struct System { impl System { pub async fn new() -> Result { + Logging::initialize()?; + + log!(SystemLog::Initializing); + let (mut ingress_ebpf, ingress_program_array) = System::get_ingress_ebpf()?; let (mut egress_ebpf, egress_program_array) = System::get_egress_ebpf()?; let app_config = Arc::new(AppConfig::new()?); @@ -73,8 +77,6 @@ impl System { pub async fn run(&mut self) -> Result<(), Error> { let ebpf_services = self.ebpf_services.clone(); let app_services = self.app_services.clone(); - Logging::initialize()?; - log!(SystemLog::Initializing); log!(MLLog::ModelsLoaded( self.app_services.ml_models.get_model_info("deep_autoencoder") @@ -85,7 +87,6 @@ impl System { }); self.aya_log_init()?; - log!(SystemLog::InitializeComplete); self.attach_ebpf()?; ebpf_services @@ -93,6 +94,8 @@ impl System { .await?; app_services.run().await?; self.run_http_server().await?; + + log!(SystemLog::InitializeComplete); Ok(()) } @@ -168,6 +171,8 @@ impl System { .await .map_err(HttpError::BindPortError)?; + log!(SystemLog::HttpServerListening { port }); + axum::serve(listener, app) .with_graceful_shutdown(async { tokio::signal::ctrl_c().await.ok(); @@ -205,6 +210,12 @@ impl System { Ebpf::load(aya::include_bytes_aligned!(env!("EGRESS_PATH"))).map_err(EbpfError::EbpfNotFound)?; let program_array = egress_ebpf.take_map("PROGRAM_ARRAY").ok_or(EbpfError::MapNotFound)?; let mut program_array = ProgramArray::try_from(program_array).map_err(EbpfError::MapOperationError)?; + Self::load_program( + &mut egress_ebpf, + &mut program_array, + "access_control", + egress::ACCESS_CONTROL, + )?; Self::load_program(&mut egress_ebpf, &mut program_array, "statistics", egress::STATISTICS)?; Self::load_program( &mut egress_ebpf, @@ -243,4 +254,4 @@ impl System { } Ok(()) } -} +} \ No newline at end of file diff --git a/mantis/src/detection/suricata/engine.rs b/mantis/src/detection/suricata/engine.rs index 3d63bae..979f158 100644 --- a/mantis/src/detection/suricata/engine.rs +++ b/mantis/src/detection/suricata/engine.rs @@ -242,7 +242,7 @@ vars: ENIP_CLIENT: "$HOME_NET" ENIP_SERVER: "$HOME_NET" port-groups: - HTTP_PORTS: "80" + HTTP_PORTS: "80,8080,8000,8008,8443,8888" SHELLCODE_PORTS: "!80" ORACLE_PORTS: 1521 SSH_PORTS: 22 @@ -293,6 +293,8 @@ app-layer: enabled: yes http: enabled: yes + detection-ports: + dp: "any" dns: enabled: yes smtp: @@ -315,7 +317,7 @@ legacy: host-mode: sniffer-only "#, - home_net = config.home_net, + home_net = config.home_net.join(","), rule_path = rule_path, eve_socket = eve_socket, suppress_path = suppress_path, diff --git a/mantis/src/model/config.rs b/mantis/src/model/config.rs index 7f4015a..6e7d8e3 100644 --- a/mantis/src/model/config.rs +++ b/mantis/src/model/config.rs @@ -8,7 +8,7 @@ pub struct ConfigTable { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SuricataConfig { - pub home_net: String, + pub home_net: Vec, pub worker_cpu_set: Option<[u32; 2]>, pub management_cpu: Option, #[serde(default = "default_af_threads")] diff --git a/mantis/src/model/log/system.rs b/mantis/src/model/log/system.rs index c4bce53..0f84874 100644 --- a/mantis/src/model/log/system.rs +++ b/mantis/src/model/log/system.rs @@ -27,5 +27,7 @@ loggable! { #[error("Traffic logging mode enabled — writing packets to: {path}")] TrafficLoggingEnabled { path: String } => tracing::Level::INFO, + #[error("HTTP server listening on 0.0.0.0:{port}")] + HttpServerListening { port: u16 } => tracing::Level::INFO, } } diff --git a/mantis/src/web/api/control/access_control.rs b/mantis/src/web/api/control/access_control.rs index 600bb33..3856c76 100644 --- a/mantis/src/web/api/control/access_control.rs +++ b/mantis/src/web/api/control/access_control.rs @@ -7,65 +7,65 @@ use axum::routing::{delete, get, put}; use axum::{Json, Router}; use crate::core::app_state::AppState; -use crate::model::direction::FlowDirection; +use crate::model::direction::{Direction, FlowDirection}; use crate::model::list_type::ListType; pub fn router() -> Router { Router::new() .route( - "/ipv4/{direction}/{list_type}", + "/{nic_direction}/ipv4/{flow_direction}/{list_type}", get(get_ipv4_list).put(add_ipv4_list).delete(remove_ipv4_list), ) .route( - "/ipv6/{direction}/{list_type}", + "/{nic_direction}/ipv6/{flow_direction}/{list_type}", get(get_ipv6_list).put(add_ipv6_list).delete(remove_ipv6_list), ) } async fn get_ipv4_list( - Path((direction, list_type)): Path<(FlowDirection, ListType)>, + Path((nic, flow, list_type)): Path<(Direction, FlowDirection, ListType)>, State(state): State, ) -> impl IntoResponse { - Json(state.access_control.get_ipv4_list(direction, list_type).await) + Json(state.access_control.get_ipv4_list(nic, flow, list_type).await) } async fn get_ipv6_list( - Path((direction, list_type)): Path<(FlowDirection, ListType)>, + Path((nic, flow, list_type)): Path<(Direction, FlowDirection, ListType)>, State(state): State, ) -> impl IntoResponse { - Json(state.access_control.get_ipv6_list(direction, list_type).await) + Json(state.access_control.get_ipv6_list(nic, flow, list_type).await) } async fn add_ipv4_list( - Path((direction, list_type)): Path<(FlowDirection, ListType)>, + Path((nic, flow, list_type)): Path<(Direction, FlowDirection, ListType)>, State(state): State, Json(address): Json, ) -> impl IntoResponse { - match state.access_control.add_ipv4_list(direction, list_type, address).await { + match state.access_control.add_ipv4_list(nic, flow, list_type, address).await { Ok(_) => StatusCode::OK.into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } async fn add_ipv6_list( - Path((direction, list_type)): Path<(FlowDirection, ListType)>, + Path((nic, flow, list_type)): Path<(Direction, FlowDirection, ListType)>, State(state): State, Json(address): Json, ) -> impl IntoResponse { - match state.access_control.add_ipv6_list(direction, list_type, address).await { + match state.access_control.add_ipv6_list(nic, flow, list_type, address).await { Ok(_) => StatusCode::OK.into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } async fn remove_ipv4_list( - Path((direction, list_type)): Path<(FlowDirection, ListType)>, + Path((nic, flow, list_type)): Path<(Direction, FlowDirection, ListType)>, State(state): State, Json(address): Json, ) -> impl IntoResponse { match state .access_control - .remove_ipv4_list(direction, list_type, address) + .remove_ipv4_list(nic, flow, list_type, address) .await { Ok(_) => StatusCode::OK.into_response(), @@ -74,13 +74,13 @@ async fn remove_ipv4_list( } async fn remove_ipv6_list( - Path((direction, list_type)): Path<(FlowDirection, ListType)>, + Path((nic, flow, list_type)): Path<(Direction, FlowDirection, ListType)>, State(state): State, Json(address): Json, ) -> impl IntoResponse { match state .access_control - .remove_ipv6_list(direction, list_type, address) + .remove_ipv6_list(nic, flow, list_type, address) .await { Ok(_) => StatusCode::OK.into_response(),