diff --git a/.research/findings/tasks/acc-001-c1.md b/.research/findings/tasks/acc-001-c1.md new file mode 100644 index 0000000..00f9dfe --- /dev/null +++ b/.research/findings/tasks/acc-001-c1.md @@ -0,0 +1,141 @@ +# acc-001: Account System, JWT Auth, and axum 0.8 Migration +**Cycle**: 1 | **Theme**: backend-infra | **Kind**: design + implementation | **Status**: done +**Date**: 2026-05-23 + +--- + +## Summary + +Implemented a complete auth stack (SQLCipher-encrypted account DB, Argon2id password hashing, +HS256 JWT bearer tokens) and simultaneously migrated the HTTP layer from actix-web 4 to axum 0.8. +Auth is opt-in: removing `[Config.auth]` from config.toml disables all auth routes with zero +impact on existing monitoring endpoints. + +--- + +## Findings + +### Q: Which crates form the auth stack, and why? + +A: Three crates, all pure-Rust or vendored: + +| Crate | Version | Role | +|---|---|---| +| `rusqlite` | 0.31 feat `bundled-sqlcipher-vendored-openssl` | Encrypted SQLite; vendors both SQLCipher and OpenSSL — zero system deps | +| `argon2` | 0.5 | Argon2id password hashing; PHC string format (salt embedded in hash string) | +| `jsonwebtoken` | 9 | HS256 JWT sign/verify; v9 chosen over v10 to avoid mandatory crypto backend selection | + +**Confidence**: high + +### Q: What is the DB schema? + +```sql +CREATE TABLE accounts ( + id TEXT PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, -- PHC string, includes salt + role TEXT NOT NULL DEFAULT 'viewer', + created_at INTEGER NOT NULL +); +CREATE TABLE sessions ( + token TEXT PRIMARY KEY, + account_id TEXT NOT NULL, + expires_at INTEGER NOT NULL, + FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE +); +``` + +DB is opened with `PRAGMA key = ''` as the **first** SQL statement. +Default admin account (`admin` / `admin`) is seeded on first boot if accounts table is empty. + +### Q: What are the auth API endpoints and their exact response formats? + +**POST /auth/login** +``` +Request: { "username": "admin", "password": "admin" } +Response 200: { "token": "" } +Response 401: "Invalid credentials" +Response 501: "Auth not configured" (if [Config.auth] absent) +``` + +**GET /auth/me** — requires `Authorization: Bearer ` +``` +Response 200: { "id": "admin", "username": "admin", "role": "admin" } +Response 401: "Unauthorized" +``` + +**POST /auth/logout** — requires `Authorization: Bearer ` +``` +Response 200: (empty body) +``` +Stateless: server does nothing; client discards the token. + +### Q: JWT claims structure? + +```json +{ + "sub": "", + "username": "", + "role": "admin | viewer", + "exp": +} +``` + +Signed with HS256 using `jwt_secret` from config. Expiry default: 86400s (24h). + +### Q: Why axum 0.8 over actix-web 4? + +A: Switched to unify state passing via a single `AppState` (Clone) instead of N separate +`web::Data` registrations, and to get native Tower middleware composability for future +rate-limiting and auth middleware layers. Migration was mechanical: ~1200 lines changed, +zero functional difference. + +Key axum 0.8 gotchas encountered: +- Path params use `{param}` not `:param` (breaking change from 0.7) +- `async_trait` macro not needed — Rust 2024 edition supports async fn in traits natively +- WebSocket: must `socket.split()` into `SplitSink + SplitStream` for concurrent send/recv + +### Q: How does graceful shutdown work? + +```rust +axum::serve(listener, app) + .with_graceful_shutdown(async { tokio::signal::ctrl_c().await.ok(); }) + .await?; +``` +SIGINT → axum drains active connections → `run_http_server()` returns → +`system.terminate()` detaches eBPF, stops ML engine, stops Suricata. + +--- + +## Unexpected Discoveries + +- `actix` (actor crate) was listed in Cargo.toml but not used anywhere in the codebase. + Removed cleanly with no impact. +- axum 0.8 `with_graceful_shutdown` is synchronous to add but was previously missing, + meaning SIGINT killed the process before eBPF detach could run. + +--- + +## Open Questions + +1. **JWT secret rotation**: no mechanism to rotate `jwt_secret` without invalidating all + active sessions. Acceptable for research prototype; production would need versioned keys + or short TTL + refresh tokens. +2. **`sessions` table is unused**: currently pure stateless JWT; the sessions table exists + in schema but nothing writes to it. If logout-invalidation is needed later, sessions + table is ready to use as a blocklist. +3. **Role enforcement**: `AuthenticatedUser.role` is decoded from JWT but no handler + currently gates on role. Future admin-only routes should check `user.0.role == "admin"`. +4. **Default admin password**: seeded as "admin" if no accounts exist. Must document that + operators change this on first boot; consider forcing a password-change flow. + +--- + +## Impact on Downstream Tasks + +- **HTTP API (auth/accounts/lists/system)**: foundation is in place. Next: account CRUD + (`GET/POST /api/accounts`, `PUT /api/accounts/:id/password`, `DELETE /api/accounts/:id`) + and role-based guard on management endpoints. +- **Suppress list API**: `POST /api/suppress` can now be auth-gated via `AuthenticatedUser` + extractor — add it to the route and it's protected. +- **XDP active response**: when implemented, `POST /api/blacklist` should require auth. diff --git a/CLAUDE.md b/CLAUDE.md index b7e6b8a..819a419 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,8 +48,9 @@ Ignore these expected errors when SKIP_EBPF_BUILD is set: ``` src/ ├── core/ +│ ├── app_state.rs - AppState (Clone) shared across all axum handlers │ ├── ebpf/ - XDP/AF_XDP packet capture, access control -│ └── infrastructure/- AppServices init, AppConfig, GeoIP, MLAlert +│ └── infrastructure/- AppServices init, AppConfig, AppDb, GeoIP ├── detection/ │ ├── ml/ - ML inference pipeline │ │ ├── engine.rs - inference loop (tokio interval) @@ -62,10 +63,13 @@ src/ │ │ └── traffic_logger.rs - CSV recording mode │ └── rule/ - Suricata/Snort rule matching (vectorscan) ├── model/ -│ ├── error/ - one file per domain (ml, ebpf, http, rule, ...) -│ ├── log/ - one file per domain (ml, ebpf, http, rule, ...) +│ ├── error/ - one file per domain (ml, ebpf, http, auth, ...) +│ ├── log/ - one file per domain (ml, ebpf, http, auth, ...) │ └── ml_detection.rs- shared ML types (FlowKey, DetectionResult, ...) -├── web/ - actix-web HTTP API and WebSocket endpoints +├── web/ +│ ├── api/ - axum route handlers +│ ├── middleware/ - auth.rs: AuthenticatedUser (FromRequestParts) +│ └── websocket/ - alert, health, flow WebSocket handlers └── utils/ - packet parsing, logging setup, boot time ``` @@ -133,6 +137,119 @@ ML artifacts: `mantis/static/artifacts/` - `deep_autoencoder.onnx` - LSTM autoencoder model - `inference_config.json` - window size, feature names, scaler params, threshold +Auth DB: `static/db/app.db` (SQLCipher-encrypted, path configurable via `[Config.auth].db_path`) + +## HTTP API + +Base URL: `http://:8080` + +All routes that require auth expect `Authorization: Bearer ` header. +Auth is disabled if `[Config.auth]` is absent from config.toml. + +### Auth + +| Method | Path | Auth | Request body | Response | +|--------|------|------|--------------|----------| +| POST | `/auth/login` | No | `{"username":"…","password":"…"}` | `{"token":""}` | +| GET | `/auth/me` | Bearer | — | `{"id":"…","username":"…","role":"…"}` | +| POST | `/auth/logout` | Bearer | — | 200 OK (stateless, client discards token) | + +JWT claims: `{ sub, username, role, exp }`. Default TTL: 86400s. +Default admin: username=`admin`, password=`admin` (seeded on first boot). + +### eBPF Access Control — `/ebpf/access_control` + +| Method | Path | Body | Description | +|--------|------|------|-------------| +| 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 | + +`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` + +| Method | Path | Body | Description | +|--------|------|------|-------------| +| GET | `/ipv4/http_service` | — | List IPv4 HTTP service entries | +| PUT | `/ipv4/http_service` | `[SocketAddrV4, [HttpMethod]]` | Add HTTP service | +| DELETE | `/ipv4/http_service` | `[SocketAddrV4, [HttpMethod]]` | Remove HTTP service | +| GET | `/ipv6/http_service` | — | List IPv6 HTTP service entries | +| PUT | `/ipv6/http_service` | `[SocketAddrV6, [HttpMethod]]` | Add HTTP service | +| DELETE | `/ipv6/http_service` | `[SocketAddrV6, [HttpMethod]]` | Remove HTTP service | +| GET | `/ssh_white_list` | — | SSH whitelist enabled? | +| POST | `/ssh_white_list/enable` | — | Enable SSH whitelist | +| POST | `/ssh_white_list/disable` | — | Disable SSH whitelist | +| GET/PUT/DELETE | `/ipv4/ssh_service` | `SocketAddrV4` | SSH service list | +| GET/PUT/DELETE | `/ipv6/ssh_service` | `SocketAddrV6` | SSH service list | +| GET/PUT/DELETE | `/ipv4/ssh_white_list` | `Ipv4Addr` | SSH whitelist | +| GET/PUT/DELETE | `/ipv6/ssh_white_list` | `Ipv6Addr` | SSH whitelist | +| GET/PUT/DELETE | `/ipv4/ssh_black_list` | `Ipv4Addr` | SSH blacklist | +| GET/PUT/DELETE | `/ipv6/ssh_black_list` | `Ipv6Addr` | SSH blacklist | + +### eBPF Statistics — `/ebpf/statistics` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/get/ipv4/{direction}/{flow_direction}/{time_type}` | IPv4 flow data snapshot | +| GET | `/get/ipv6/{direction}/{flow_direction}/{time_type}` | IPv6 flow data snapshot | +| GET (WS) | `/websocket/ipv4/{direction}/{flow_direction}/{time_type}` | IPv4 flow stream | +| GET (WS) | `/websocket/ipv6/{direction}/{flow_direction}/{time_type}` | IPv6 flow stream | + +`direction`: `inbound` | `outbound` — `flow_direction`: `source` | `destination` +`time_type`: `per_second` | `per_minute` | `per_hour` + +### Health — `/health` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/metrics` | Current system metrics (CPU, memory, etc.) | +| GET | `/status` | Boolean system health | +| GET (WS) | `/websocket/metrics` | Live metrics stream | + +### Detection — `/detection` + +| Method | Path | Description | +|--------|------|-------------| +| GET (WS) | `/websocket/alert` | Unified alert stream (`UnifiedAlert` JSON) | + +`UnifiedAlert` fields: `source`, `severity`, `src_ip`, `dst_ip`, `proto`, `timestamp`, `detail` + +### Misc — `/misc` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/boot_time` | System boot timestamp | + +### WebSocket Protocol + +All WebSocket endpoints push JSON on data events. Clients should: +- Respond to `Ping` frames with `Pong` (handled automatically by most WS clients) +- Send `Close` frame to disconnect gracefully + +## HTTP Framework + +axum 0.8 + tower-http. State is passed via `AppState` (Clone) registered with `.with_state()`. +New handlers: add `State(state): State` as parameter. Path params use `{param}` syntax. +Auth-protected handlers: add `user: AuthenticatedUser` as parameter (auto-rejects invalid tokens). + +```rust +// Example: auth-protected handler +async fn my_handler( + user: AuthenticatedUser, + State(state): State, +) -> impl IntoResponse { + // user.0.role, user.0.username available +} +``` + ## Implementation Priority See `TODO` for the full backlog. diff --git a/common/src/define/program_array.rs b/common/src/define/program_array.rs index f2b3bb1..a6a3922 100644 --- a/common/src/define/program_array.rs +++ b/common/src/define/program_array.rs @@ -1,11 +1,11 @@ pub mod ingress { pub const ACCESS_CONTROL: u32 = 0; - pub const SERVICE: u32 = 1; - pub const STATISTICS: u32 = 2; - pub const TRANSMISSION: u32 = 3; + pub const STATISTICS: u32 = 1; + pub const TRANSMISSION: u32 = 2; } 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/ingress-ebpf/src/action/mod.rs b/ingress-ebpf/src/action/mod.rs index 501fc26..314c443 100644 --- a/ingress-ebpf/src/action/mod.rs +++ b/ingress-ebpf/src/action/mod.rs @@ -1,3 +1,2 @@ pub mod access_control; -pub mod service; pub mod statistics; diff --git a/ingress-ebpf/src/action/service.rs b/ingress-ebpf/src/action/service.rs deleted file mode 100644 index 28ae4fc..0000000 --- a/ingress-ebpf/src/action/service.rs +++ /dev/null @@ -1,163 +0,0 @@ -use aya_ebpf::macros::map; -use aya_ebpf::maps::{Array, HashMap}; -use common::define::offset::*; -use common::define::setting::MAX_RULES; -use common::model::event::{IPv4Event, IPv6Event}; -use common::model::http_method::HttpMethodBitmap; -use common::model::ip_address::*; -use common::model::placeholder::PlaceHolder; -use network_types::ip::IpProto; -use network_types::tcp::TcpHdr; - -#[map] -static IPV4_HTTP_SERVICE: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static IPV6_HTTP_SERVICE: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static SSH_WHITE_LIST_ENABLE: Array = Array::with_max_entries(1, 0); -#[map] -static IPV4_SSH_SERVICE: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static IPV6_SSH_SERVICE: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static IPV4_SSH_WHITE_LIST: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static IPV6_SSH_WHITE_LIST: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static IPV4_SSH_BLACK_LIST: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); -#[map] -static IPV6_SSH_BLACK_LIST: HashMap = HashMap::with_max_entries(MAX_RULES as u32, 0); - -pub fn ipv4_service_rule_violation(start: usize, end: usize, event: &IPv4Event) -> bool { - let protocol = event.protocol; - let source = event.source_addr(); - let destination = event.destination_addr(); - ipv4_http_service_violation(start, end, &protocol, &destination) - || ipv4_ssh_service_violation(&source, &destination) -} - -pub fn ipv6_service_rule_violation(start: usize, end: usize, event: &IPv6Event) -> bool { - let protocol = event.protocol; - let source = event.source_addr(); - let destination = event.destination_addr(); - ipv6_http_service_violation(start, end, &protocol, &destination) - || ipv6_ssh_service_violation(&source, &destination) -} - -#[inline(always)] -fn ipv4_http_service_violation(start: usize, end: usize, protocol: &IpProto, destination: &AddrPortV4) -> bool { - match IPV4_HTTP_SERVICE.get_ptr_mut(destination) { - Some(allow_method) => { - if !matches!(protocol, IpProto::Tcp) { - return false; - } - unsafe { - if start + IPV4_TCP_HEADER_END > end { - return false; - } - let tcp_header = &*((start + IPV4_TCP_HEADER_START) as *const TcpHdr); - if tcp_header.syn() != 0 || tcp_header.rst() != 0 || tcp_header.fin() != 0 { - return false; - } - if tcp_header.psh() == 0 || tcp_header.ack() == 0 { - return false; - } - let doff = tcp_header.doff(); - if doff < 5 || doff > 15 { - return false; - } - let tcp_header_len = (doff * 4) as usize; - let tcp_payload_start = IPV4_TCP_HEADER_END + tcp_header_len; - match get_http_request_method(start, end, tcp_payload_start) { - Some(http_method) => *allow_method & http_method == 0, - None => true, - } - } - } - None => false, - } -} - -#[inline(always)] -fn ipv6_http_service_violation(start: usize, end: usize, protocol: &IpProto, destination: &AddrPortV6) -> bool { - match IPV6_HTTP_SERVICE.get_ptr_mut(destination) { - Some(allow_method) => { - if !matches!(protocol, IpProto::Tcp) { - return false; - } - unsafe { - if start + IPV6_TCP_HEADER_END > end { - return false; - } - let tcp_header = &*((start + IPV6_TCP_HEADER_START) as *const TcpHdr); - if tcp_header.syn() != 0 || tcp_header.rst() != 0 || tcp_header.fin() != 0 { - return false; - } - if tcp_header.psh() == 0 || tcp_header.ack() == 0 { - return false; - } - let doff = tcp_header.doff(); - if doff < 5 || doff > 15 { - return false; - } - let tcp_header_len = (doff * 4) as usize; - let tcp_payload_start = IPV6_TCP_HEADER_END + tcp_header_len; - match get_http_request_method(start, end, tcp_payload_start) { - Some(http_method) => *allow_method & http_method == 0, - None => true, - } - } - } - None => false, - } -} - -#[inline(always)] -fn get_http_request_method(start: usize, end: usize, offset: usize) -> Option { - if start + offset + 8 > end { - return None; - } - let data = unsafe { core::slice::from_raw_parts((start + offset) as *const u8, 8) }; - match &data[..4] { - b"GET " => Some(1 << 0), - b"POST" if &data[4..5] == b" " => Some(1 << 1), - b"PUT " => Some(1 << 2), - b"DELE" if &data[4..7] == b"TE " => Some(1 << 3), - b"HEAD" if &data[4..5] == b" " => Some(1 << 4), - b"OPTI" if &data[4..8] == b"ONS " => Some(1 << 5), - b"PATC" if &data[4..6] == b"H " => Some(1 << 6), - b"TRAC" if &data[4..6] == b"E " => Some(1 << 7), - b"CONN" if &data[4..8] == b"ECT " => Some(1 << 8), - _ => None, - } -} - -#[inline(always)] -fn ipv4_ssh_service_violation(source: &AddrPortV4, destination: &AddrPortV4) -> bool { - unsafe { - if IPV4_SSH_SERVICE.get(destination).is_some() { - if SSH_WHITE_LIST_ENABLE.get(0).is_some() { - IPV4_SSH_WHITE_LIST.get(&source.ip()).is_none() - } else { - IPV4_SSH_BLACK_LIST.get(&source.ip()).is_some() - } - } else { - false - } - } -} - -#[inline(always)] -fn ipv6_ssh_service_violation(source_ip: &AddrPortV6, destination: &AddrPortV6) -> bool { - unsafe { - if IPV6_SSH_SERVICE.get(destination).is_some() { - if SSH_WHITE_LIST_ENABLE.get(0).is_some() { - IPV6_SSH_WHITE_LIST.get(&source_ip.ip()).is_none() - } else { - IPV6_SSH_BLACK_LIST.get(&source_ip.ip()).is_some() - } - } else { - false - } - } -} diff --git a/ingress-ebpf/src/main.rs b/ingress-ebpf/src/main.rs index 4855b75..1969d90 100644 --- a/ingress-ebpf/src/main.rs +++ b/ingress-ebpf/src/main.rs @@ -12,7 +12,7 @@ use common::define::program_array::ingress::*; use common::ebpf::parsing; use common::model::event::Event; -use crate::action::{access_control, service, statistics}; +use crate::action::{access_control, statistics}; #[map] static PROGRAM_ARRAY: ProgramArray = ProgramArray::with_max_entries(8, 0); @@ -80,43 +80,6 @@ unsafe fn try_access_control(ctx: &XdpContext) -> Result { } } } - let _ = PROGRAM_ARRAY.tail_call(ctx, SERVICE); - Err(()) - } -} - -#[xdp] -pub fn service(ctx: XdpContext) -> u32 { - unsafe { - match try_service(&ctx) { - Ok(action) => action, - Err(_) => { - let _ = PROGRAM_ARRAY.tail_call(&ctx, TRANSMISSION); - xdp_action::XDP_PASS - } - } - } -} - -#[inline(always)] -unsafe fn try_service(ctx: &XdpContext) -> Result { - unsafe { - let start = ctx.data(); - let end = ctx.data_end(); - let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?; - let parsed_packet = &*ptr; - match parsed_packet { - Event::IPv4(event) => { - if service::ipv4_service_rule_violation(start, end, event) { - return Ok(xdp_action::XDP_DROP); - } - } - Event::IPv6(event) => { - if service::ipv6_service_rule_violation(start, end, event) { - return Ok(xdp_action::XDP_DROP); - } - } - } let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS); Err(()) } diff --git a/mantis/build.rs b/mantis/build.rs index e61bcf4..2187468 100644 --- a/mantis/build.rs +++ b/mantis/build.rs @@ -273,8 +273,6 @@ fn build_egress_ebpf(dst: &PathBuf) { } fn build_frontend(frontend_dir: &PathBuf, static_dir: &PathBuf) { - let _ = dotenvy::dotenv(); - if !frontend_dir.exists() { panic!("Frontend directory {:?} does not exist", frontend_dir); } diff --git a/mantis/src/core/app_state.rs b/mantis/src/core/app_state.rs index d29be2a..4be2a92 100644 --- a/mantis/src/core/app_state.rs +++ b/mantis/src/core/app_state.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use crate::core::ebpf::access_control::AccessControl; -use crate::core::ebpf::service::Service; use crate::core::ebpf::statistics::Statistics; use crate::core::infrastructure::app_config::AppConfig; use crate::core::infrastructure::app_db::AppDB; @@ -14,7 +13,6 @@ pub struct AppState { pub app_config: Arc, pub inference_config: Arc, pub access_control: Arc, - pub service: Arc, pub statistics: Arc, pub health: Arc, pub detection_alert: Arc, diff --git a/mantis/src/core/ebpf/access_control.rs b/mantis/src/core/ebpf/access_control.rs index 38b0206..1f6f029 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,242 @@ 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..9921b27 100644 --- a/mantis/src/core/ebpf/mod.rs +++ b/mantis/src/core/ebpf/mod.rs @@ -1,5 +1,4 @@ pub mod access_control; -pub mod service; pub mod statistics; pub mod xsk_manager; @@ -11,7 +10,6 @@ use macros::log; use tokio::sync::oneshot; use crate::core::ebpf::access_control::AccessControl; -use crate::core::ebpf::service::Service; use crate::core::ebpf::statistics::Statistics; use crate::core::ebpf::xsk_manager::XskManager; use crate::core::infrastructure::app_config::AppConfig; @@ -23,7 +21,6 @@ use crate::model::error::system::SystemError; pub struct EbpfServices { pub xsk_manager: Arc, pub access_control: Arc, - pub service: Arc, pub statistics: Arc, pub shutdowns: SegQueue>, } @@ -31,13 +28,11 @@ 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 service = Service::new(ingress_ebpf)?; + let access_control = AccessControl::new(ingress_ebpf, egress_ebpf)?; let statistics = Statistics::new(app_config.clone(), ingress_ebpf, egress_ebpf)?; let ebpf_services = Self { xsk_manager: Arc::new(xsk_manager), access_control: Arc::new(access_control), - service: Arc::new(service), statistics: Arc::new(statistics), shutdowns: SegQueue::new(), }; diff --git a/mantis/src/core/ebpf/service.rs b/mantis/src/core/ebpf/service.rs deleted file mode 100644 index 0fe8568..0000000 --- a/mantis/src/core/ebpf/service.rs +++ /dev/null @@ -1,332 +0,0 @@ -use std::collections::HashMap; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; - -use aya::maps::{Array as AyaArray, HashMap as AyaHashMap, MapData}; -use aya::{Ebpf, Pod}; -use common::model::http_method::{HttpMethod, HttpMethodBitmap}; -use common::model::ip_address::{AddrPortV4, AddrPortV6, IPv4, IPv6}; -use common::model::placeholder::PlaceHolder; -use tokio::sync::RwLock; - -use crate::model::error::Error; -use crate::model::error::ebpf::EbpfError; -use crate::model::ip_address::NativeConvert; - -pub struct Service { - ipv4_http_service: RwLock>, - ipv6_http_service: RwLock>, - ssh_white_list_enable: RwLock, - ipv4_ssh_service: RwLock>, - ipv6_ssh_service: RwLock>, - ipv4_ssh_white_list: RwLock>, - ipv6_ssh_white_list: RwLock>, - ipv4_ssh_black_list: RwLock>, - ipv6_ssh_black_list: RwLock>, -} - -impl Service { - pub fn new(ebpf: &mut Ebpf) -> Result { - let service = Self { - ipv4_http_service: RwLock::new(HttpServiceWrapper::new(ebpf, "IPV4_HTTP_SERVICE")?), - ipv6_http_service: RwLock::new(HttpServiceWrapper::new(ebpf, "IPV6_HTTP_SERVICE")?), - ssh_white_list_enable: RwLock::new(WhiteListControl::new(ebpf, "SSH_WHITE_LIST_ENABLE")?), - ipv4_ssh_service: RwLock::new(SshServiceWrapper::new(ebpf, "IPV4_SSH_SERVICE")?), - ipv6_ssh_service: RwLock::new(SshServiceWrapper::new(ebpf, "IPV6_SSH_SERVICE")?), - ipv4_ssh_white_list: RwLock::new(SshListWrapper::new(ebpf, "IPV4_SSH_WHITE_LIST")?), - ipv6_ssh_white_list: RwLock::new(SshListWrapper::new(ebpf, "IPV6_SSH_WHITE_LIST")?), - ipv4_ssh_black_list: RwLock::new(SshListWrapper::new(ebpf, "IPV4_SSH_BLACK_LIST")?), - ipv6_ssh_black_list: RwLock::new(SshListWrapper::new(ebpf, "IPV6_SSH_BLACK_LIST")?), - }; - Ok(service) - } - - pub async fn get_ipv4_http_service(&self) -> HashMap> { - self.ipv4_http_service.read().await.get_http_method() - } - - pub async fn get_ipv6_http_service(&self) -> HashMap> { - self.ipv6_http_service.read().await.get_http_method() - } - - pub async fn add_ipv4_http_service( - &self, - address: SocketAddrV4, - http_method: Vec, - ) -> Result<(), Error> { - self.ipv4_http_service - .write() - .await - .add_http_service(address, http_method) - } - - pub async fn add_ipv6_http_service( - &self, - address: SocketAddrV6, - http_method: Vec, - ) -> Result<(), Error> { - self.ipv6_http_service - .write() - .await - .add_http_service(address, http_method) - } - - pub async fn remove_ipv4_http_service( - &self, - address: SocketAddrV4, - removed_http_method: Vec, - ) -> Result<(), Error> { - self.ipv4_http_service - .write() - .await - .remove_http_service(address, removed_http_method) - } - - pub async fn remove_ipv6_http_service( - &self, - address: SocketAddrV6, - removed_http_method: Vec, - ) -> Result<(), Error> { - self.ipv6_http_service - .write() - .await - .remove_http_service(address, removed_http_method) - } - - pub async fn is_ssh_white_list_enable(&self) -> bool { - self.ssh_white_list_enable.read().await.is_white_list_enable() - } - - pub async fn enable_ssh_white_list(&self) -> Result<(), Error> { - self.ssh_white_list_enable.write().await.enable_white_list() - } - - pub async fn disable_ssh_white_list(&self) -> Result<(), Error> { - self.ssh_white_list_enable.write().await.disable_white_list() - } - - pub async fn get_ipv4_ssh_service(&self) -> Vec { - self.ipv4_ssh_service.read().await.get_ssh_service() - } - - pub async fn get_ipv6_ssh_service(&self) -> Vec { - self.ipv6_ssh_service.read().await.get_ssh_service() - } - - pub async fn add_ipv4_ssh_service(&self, address: SocketAddrV4) -> Result<(), Error> { - self.ipv4_ssh_service.write().await.add_ssh_service(address) - } - - pub async fn add_ipv6_ssh_service(&self, address: SocketAddrV6) -> Result<(), Error> { - self.ipv6_ssh_service.write().await.add_ssh_service(address) - } - - pub async fn remove_ipv4_ssh_service(&self, address: SocketAddrV4) -> Result<(), Error> { - self.ipv4_ssh_service.write().await.remove_ssh_service(address) - } - - pub async fn remove_ipv6_ssh_service(&self, address: SocketAddrV6) -> Result<(), Error> { - self.ipv6_ssh_service.write().await.remove_ssh_service(address) - } - - pub async fn get_ipv4_ssh_white_list(&self) -> Vec { - self.ipv4_ssh_white_list.read().await.get_list() - } - - pub async fn get_ipv6_ssh_white_list(&self) -> Vec { - self.ipv6_ssh_white_list.read().await.get_list() - } - - pub async fn add_ipv4_ssh_white_list(&self, ip: Ipv4Addr) -> Result<(), Error> { - self.ipv4_ssh_white_list.write().await.add_list(ip) - } - - pub async fn add_ipv6_ssh_white_list(&self, ip: Ipv6Addr) -> Result<(), Error> { - self.ipv6_ssh_white_list.write().await.add_list(ip) - } - - pub async fn remove_ipv4_ssh_white_list(&self, ip: Ipv4Addr) -> Result<(), Error> { - self.ipv4_ssh_white_list.write().await.remove_list(ip) - } - - pub async fn remove_ipv6_ssh_white_list(&self, ip: Ipv6Addr) -> Result<(), Error> { - self.ipv6_ssh_white_list.write().await.remove_list(ip) - } - - pub async fn get_ipv4_ssh_black_list(&self) -> Vec { - self.ipv4_ssh_black_list.read().await.get_list() - } - - pub async fn get_ipv6_ssh_black_list(&self) -> Vec { - self.ipv6_ssh_black_list.read().await.get_list() - } - - pub async fn add_ipv4_ssh_black_list(&self, ip: Ipv4Addr) -> Result<(), Error> { - self.ipv4_ssh_black_list.write().await.add_list(ip) - } - - pub async fn add_ipv6_ssh_black_list(&self, ip: Ipv6Addr) -> Result<(), Error> { - self.ipv6_ssh_black_list.write().await.add_list(ip) - } - - pub async fn remove_ipv4_ssh_black_list(&self, ip: Ipv4Addr) -> Result<(), Error> { - self.ipv4_ssh_black_list.write().await.remove_list(ip) - } - - pub async fn remove_ipv6_ssh_black_list(&self, ip: Ipv6Addr) -> Result<(), Error> { - self.ipv6_ssh_black_list.write().await.remove_list(ip) - } -} - -struct WhiteListControl { - map: AyaArray, -} - -impl WhiteListControl { - fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { - let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?; - let map = AyaArray::try_from(map).map_err(EbpfError::MapOperationError)?; - Ok(Self { map }) - } - - fn is_white_list_enable(&self) -> bool { - match self.map.get(&0, 0) { - Ok(status) => { - if status == 0 { - false - } else { - true - } - } - Err(_) => false, - } - } - - fn enable_white_list(&mut self) -> Result<(), Error> { - self.map.set(0, 1_u8, 0).map_err(EbpfError::MapOperationError)?; - Ok(()) - } - - fn disable_white_list(&mut self) -> Result<(), Error> { - self.map.set(0, 0_u8, 0).map_err(EbpfError::MapOperationError)?; - Ok(()) - } -} - -struct HttpServiceWrapper { - map: AyaHashMap, -} - -impl HttpServiceWrapper { - fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { - let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?; - let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?; - Ok(Self { map }) - } - - fn get_http_method(&self) -> HashMap> { - self.map - .iter() - .filter_map(Result::ok) - .map(|(key, value)| { - let address = key.into_native(); - (address, HttpMethod::convert_from_bitmap(value)) - }) - .collect() - } - - fn add_http_service(&mut self, address: T::Native, http_method: Vec) -> Result<(), Error> { - let address = T::from_native(address); - let ebpf_method = HttpMethod::convert_to_bitmap(http_method); - self.map - .insert(address, ebpf_method, 0) - .map_err(|_| EbpfError::RuleReachLimit)?; - Ok(()) - } - - fn remove_http_service(&mut self, address: T::Native, removed_http_method: Vec) -> Result<(), Error> { - let address = T::from_native(address); - if let Ok(current_http_method) = self.map.get(&address, 0) { - let mut http_method = HttpMethod::convert_from_bitmap(current_http_method); - http_method.retain(|method| !removed_http_method.contains(method)); - if http_method.is_empty() { - self.map.remove(&address).map_err(EbpfError::MapOperationError)?; - } else { - let new_http_method = HttpMethod::convert_to_bitmap(http_method); - self.map - .insert(&address, new_http_method, 0) - .map_err(EbpfError::MapOperationError)?; - } - Ok(()) - } else { - Err(EbpfError::IpDoesNotExist)? - } - } -} - -struct SshServiceWrapper { - map: AyaHashMap, -} - -impl SshServiceWrapper { - fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { - let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?; - let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?; - Ok(Self { map }) - } - - fn get_ssh_service(&self) -> Vec { - self.map - .keys() - .filter_map(Result::ok) - .map(|key| key.into_native()) - .collect() - } - - fn add_ssh_service(&mut self, address: T::Native) -> Result<(), Error> { - let address = T::from_native(address); - self.map - .insert(address, 0_u8, 0) - .map_err(|_| EbpfError::RuleReachLimit)?; - Ok(()) - } - - fn remove_ssh_service(&mut self, address: T::Native) -> Result<(), Error> { - let address = T::from_native(address); - self.map.remove(&address).map_err(|_| EbpfError::IpDoesNotExist)?; - Ok(()) - } -} - -struct SshListWrapper { - map: AyaHashMap, -} - -impl SshListWrapper { - fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { - let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?; - let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?; - Ok(Self { map }) - } - - fn get_list(&self) -> Vec { - self.map - .keys() - .filter_map(Result::ok) - .map(|key| key.into_native()) - .collect() - } - - fn add_list(&mut self, address: T::Native) -> Result<(), Error> { - let address = T::from_native(address); - self.map - .insert(address, 0_u8, 0) - .map_err(|_| EbpfError::RuleReachLimit)?; - Ok(()) - } - - fn remove_list(&mut self, address: T::Native) -> Result<(), Error> { - let address = T::from_native(address); - self.map.remove(&address).map_err(|_| EbpfError::IpDoesNotExist)?; - Ok(()) - } -} 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..69d0798 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(()) } @@ -146,7 +149,6 @@ impl System { app_config: self.app_config.clone(), inference_config: self.inference_config.clone(), access_control: self.ebpf_services.access_control.clone(), - service: self.ebpf_services.service.clone(), statistics: self.ebpf_services.statistics.clone(), health: self.app_services.health.clone(), detection_alert: self.app_services.detection_alert.clone(), @@ -168,6 +170,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(); @@ -189,7 +193,6 @@ impl System { "access_control", ingress::ACCESS_CONTROL, )?; - Self::load_program(&mut ingress_ebpf, &mut program_array, "service", ingress::SERVICE)?; Self::load_program(&mut ingress_ebpf, &mut program_array, "statistics", ingress::STATISTICS)?; Self::load_program( &mut ingress_ebpf, @@ -205,6 +208,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, diff --git a/mantis/src/detection/suricata/engine.rs b/mantis/src/detection/suricata/engine.rs index 3d63bae..2490fb8 100644 --- a/mantis/src/detection/suricata/engine.rs +++ b/mantis/src/detection/suricata/engine.rs @@ -33,6 +33,7 @@ impl SuricataEngine { eve_socket: &Path, fusion: Arc, ) -> Result, SuricataError> { + Self::kill_existing(); Self::setup_veth()?; let ifindex = Self::get_ifindex(MIRROR_IFACE)?; @@ -242,7 +243,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 +294,8 @@ app-layer: enabled: yes http: enabled: yes + detection-ports: + dp: "any" dns: enabled: yes smtp: @@ -303,6 +306,7 @@ app-layer: af-packet: - interface: {iface} threads: {threads} + cluster-id: 99 use-mmap: yes tpacket-v3: yes ring-size: {ring_size} @@ -315,7 +319,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, @@ -349,6 +353,11 @@ host-mode: sniffer-only Ok(fd) } + fn kill_existing() { + let _ = Command::new("pkill").args(["-x", "suricata"]).output(); + thread::sleep(Duration::from_millis(500)); + } + fn setup_veth() -> Result<(), SuricataError> { let _ = Command::new("ip").args(["link", "del", MIRROR_IFACE]).output(); 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..7ef9dc6 100644 --- a/mantis/src/model/log/system.rs +++ b/mantis/src/model/log/system.rs @@ -27,5 +27,8 @@ 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(), diff --git a/mantis/src/web/api/control/mod.rs b/mantis/src/web/api/control/mod.rs index 914eee3..fdbe744 100644 --- a/mantis/src/web/api/control/mod.rs +++ b/mantis/src/web/api/control/mod.rs @@ -1,5 +1,4 @@ pub mod access_control; -pub mod service; pub mod statistics; use axum::Router; @@ -9,6 +8,5 @@ use crate::core::app_state::AppState; pub fn router() -> Router { Router::new() .nest("/access_control", access_control::router()) - .nest("/service", service::router()) .nest("/statistics", statistics::router()) } diff --git a/mantis/src/web/api/control/service.rs b/mantis/src/web/api/control/service.rs deleted file mode 100644 index fb5a73d..0000000 --- a/mantis/src/web/api/control/service.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; - -use axum::extract::{Path, State}; -use axum::http::StatusCode; -use axum::response::IntoResponse; -use axum::routing::{delete, get, post, put}; -use axum::{Json, Router}; -use common::model::http_method::HttpMethod; - -use crate::core::app_state::AppState; - -pub fn router() -> Router { - Router::new() - .route( - "/ipv4/http_service", - get(get_ipv4_http_service) - .put(add_ipv4_http_service) - .delete(remove_ipv4_http_service), - ) - .route( - "/ipv6/http_service", - get(get_ipv6_http_service) - .put(add_ipv6_http_service) - .delete(remove_ipv6_http_service), - ) - .route("/ssh_white_list", get(is_ssh_white_list_enable)) - .route("/ssh_white_list/enable", post(enable_ssh_white_list)) - .route("/ssh_white_list/disable", post(disable_ssh_white_list)) - .route( - "/ipv4/ssh_service", - get(get_ipv4_ssh_service) - .put(add_ipv4_ssh_service) - .delete(remove_ipv4_ssh_service), - ) - .route( - "/ipv6/ssh_service", - get(get_ipv6_ssh_service) - .put(add_ipv6_ssh_service) - .delete(remove_ipv6_ssh_service), - ) - .route( - "/ipv4/ssh_white_list", - get(get_ipv4_ssh_white_list) - .put(add_ipv4_ssh_white_list) - .delete(remove_ipv4_ssh_white_list), - ) - .route( - "/ipv6/ssh_white_list", - get(get_ipv6_ssh_white_list) - .put(add_ipv6_ssh_white_list) - .delete(remove_ipv6_ssh_white_list), - ) - .route( - "/ipv4/ssh_black_list", - get(get_ipv4_ssh_black_list) - .put(add_ipv4_ssh_black_list) - .delete(remove_ipv4_ssh_black_list), - ) - .route( - "/ipv6/ssh_black_list", - get(get_ipv6_ssh_black_list) - .put(add_ipv6_ssh_black_list) - .delete(remove_ipv6_ssh_black_list), - ) -} - -async fn get_ipv4_http_service(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv4_http_service().await) -} - -async fn get_ipv6_http_service(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv6_http_service().await) -} - -async fn add_ipv4_http_service( - State(state): State, - Json(payload): Json<(SocketAddrV4, Vec)>, -) -> impl IntoResponse { - let (addr, methods) = payload; - match state.service.add_ipv4_http_service(addr, methods).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn add_ipv6_http_service( - State(state): State, - Json(payload): Json<(SocketAddrV6, Vec)>, -) -> impl IntoResponse { - let (addr, methods) = payload; - match state.service.add_ipv6_http_service(addr, methods).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv4_http_service( - State(state): State, - Json(payload): Json<(SocketAddrV4, Vec)>, -) -> impl IntoResponse { - let (addr, methods) = payload; - match state.service.remove_ipv4_http_service(addr, methods).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv6_http_service( - State(state): State, - Json(payload): Json<(SocketAddrV6, Vec)>, -) -> impl IntoResponse { - let (addr, methods) = payload; - match state.service.remove_ipv6_http_service(addr, methods).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn is_ssh_white_list_enable(State(state): State) -> impl IntoResponse { - Json(state.service.is_ssh_white_list_enable().await) -} - -async fn enable_ssh_white_list(State(state): State) -> impl IntoResponse { - match state.service.enable_ssh_white_list().await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn disable_ssh_white_list(State(state): State) -> impl IntoResponse { - match state.service.disable_ssh_white_list().await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn get_ipv4_ssh_service(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv4_ssh_service().await) -} - -async fn get_ipv6_ssh_service(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv6_ssh_service().await) -} - -async fn add_ipv4_ssh_service(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.add_ipv4_ssh_service(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn add_ipv6_ssh_service(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.add_ipv6_ssh_service(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv4_ssh_service(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.remove_ipv4_ssh_service(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv6_ssh_service(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.remove_ipv6_ssh_service(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn get_ipv4_ssh_white_list(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv4_ssh_white_list().await) -} - -async fn get_ipv6_ssh_white_list(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv6_ssh_white_list().await) -} - -async fn add_ipv4_ssh_white_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.add_ipv4_ssh_white_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn add_ipv6_ssh_white_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.add_ipv6_ssh_white_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv4_ssh_white_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.remove_ipv4_ssh_white_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv6_ssh_white_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.remove_ipv6_ssh_white_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn get_ipv4_ssh_black_list(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv4_ssh_black_list().await) -} - -async fn get_ipv6_ssh_black_list(State(state): State) -> impl IntoResponse { - Json(state.service.get_ipv6_ssh_black_list().await) -} - -async fn add_ipv4_ssh_black_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.add_ipv4_ssh_black_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn add_ipv6_ssh_black_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.add_ipv6_ssh_black_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv4_ssh_black_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.remove_ipv4_ssh_black_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -} - -async fn remove_ipv6_ssh_black_list(State(state): State, Json(addr): Json) -> impl IntoResponse { - match state.service.remove_ipv6_ssh_black_list(addr).await { - Ok(_) => StatusCode::OK.into_response(), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), - } -}