mirror of
https://github.com/ParrotXray/Mantis.git
synced 2026-08-24 19:00:27 +09:00
* wip * feat: Add egress eBPF access control and fix Suricata HTTP port detection * feat: adjust code with rustfmt * feat: adjust code with rustfmt * feat: Remove SERVICE stage from ingress eBPF pipeline
256 lines
10 KiB
Markdown
256 lines
10 KiB
Markdown
# Mantis
|
|
|
|
Network intrusion detection system combining eBPF packet capture with ML-based anomaly detection and Suricata/Snort rule matching.
|
|
|
|
## Language Convention
|
|
- **Conversation**: Always use Traditional Chinese (繁體中文)
|
|
- **Files, docs, comments, code**: Always use English
|
|
- This applies to ALL generated content without exception
|
|
|
|
## Dependency Policy
|
|
|
|
- ML inference: ort-tract (pure Rust, no native ORT binary)
|
|
Do not switch to native ORT without explicit discussion.
|
|
- Async runtime: tokio only, do not add async-std or smol.
|
|
- Logging: macros::log! only, do not add log crate or use tracing:: directly.
|
|
- Locking: std::sync::Mutex is preferred; parking_lot::Mutex where performance matters.
|
|
- ort version is pinned to =2.0.0-rc.12, do not upgrade without checking ort-tract compatibility.
|
|
|
|
## Workspace Structure
|
|
|
|
```
|
|
mantis/ - Main application (ML, eBPF userspace, HTTP API, WebSocket)
|
|
common/ - Shared types used by both userspace and eBPF programs
|
|
macros/ - Procedural macros: log!, traceable!, loggable!
|
|
ingress-ebpf/ - eBPF ingress packet capture program
|
|
egress-ebpf/ - eBPF egress packet capture program
|
|
mantis-frontend/ - Next.js web UI
|
|
```
|
|
|
|
## Build
|
|
|
|
Full build requires eBPF toolchain and system libs (libelf, boost for vectorscan).
|
|
|
|
To type-check without eBPF (for ML/API changes):
|
|
```bash
|
|
SKIP_EBPF_BUILD=1 cargo check --package mantis
|
|
```
|
|
|
|
Ignore these expected errors when SKIP_EBPF_BUILD is set:
|
|
- `environment variable ARTIFACTCS_PATH not defined`
|
|
- `environment variable CSV_RECORD_PATH not defined`
|
|
- `environment variable RULES_DB_PATH not defined`
|
|
- `couldn't read .../mantis-ingress`
|
|
- `couldn't read .../mantis-egress`
|
|
|
|
## mantis Source Layout
|
|
|
|
```
|
|
src/
|
|
├── core/
|
|
│ ├── app_state.rs - AppState (Clone) shared across all axum handlers
|
|
│ ├── ebpf/ - XDP/AF_XDP packet capture, access control
|
|
│ └── infrastructure/- AppServices init, AppConfig, AppDb, GeoIP
|
|
├── detection/
|
|
│ ├── ml/ - ML inference pipeline
|
|
│ │ ├── engine.rs - inference loop (tokio interval)
|
|
│ │ ├── flow_tracker.rs - per-flow packet aggregation
|
|
│ │ ├── feature_extractor.rs - flow -> feature vector
|
|
│ │ ├── inference.rs - sliding window + autoencoder MSE
|
|
│ │ ├── model_loader.rs - ort Session creation
|
|
│ │ ├── config_loader.rs - inference_config.json
|
|
│ │ ├── aggregator.rs - attack event dedup/aggregation
|
|
│ │ └── traffic_logger.rs - CSV recording mode
|
|
│ └── rule/ - Suricata/Snort rule matching (vectorscan)
|
|
├── model/
|
|
│ ├── 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/
|
|
│ ├── api/ - axum route handlers
|
|
│ ├── middleware/ - auth.rs: AuthenticatedUser (FromRequestParts)
|
|
│ └── websocket/ - alert, health, flow WebSocket handlers
|
|
└── utils/ - packet parsing, logging setup, boot time
|
|
```
|
|
|
|
## Macros (always use these)
|
|
|
|
### log!
|
|
Logs an error or loggable value. Routes to tracing level automatically.
|
|
```rust
|
|
use macros::log;
|
|
log!(MLError::ModelLoadFailed { path });
|
|
log!(MLLog::InferenceCompleted { total_flows, anomaly, benign, duration_ms, throughput });
|
|
```
|
|
|
|
### traceable!
|
|
Defines an error enum in `src/model/error/<domain>.rs`.
|
|
```rust
|
|
traceable! {
|
|
MLError {
|
|
#[no_source]
|
|
#[error("Failed to load ONNX model from: {path:?}")]
|
|
ModelLoadFailed { path: PathBuf } => tracing::Level::ERROR,
|
|
}
|
|
}
|
|
```
|
|
|
|
### loggable!
|
|
Defines a log message enum in `src/model/log/<domain>.rs`.
|
|
```rust
|
|
loggable! {
|
|
MLLog {
|
|
#[error("ML artifacts loaded - {info}")]
|
|
ModelsLoaded { info: String } => tracing::Level::INFO,
|
|
}
|
|
}
|
|
```
|
|
|
|
## Conventions
|
|
|
|
- New error variant -> add to `src/model/error/<domain>.rs` using `traceable!`
|
|
- New log message -> add to `src/model/log/<domain>.rs` using `loggable!`
|
|
- Never use `println!`, `eprintln!`, or `tracing::` directly — always use `log!()`
|
|
- Shared data types (structs, enums used across modules) go in `src/model/`
|
|
- Comments in English only, no special characters
|
|
|
|
## ML Pipeline
|
|
|
|
Inference runs on a tokio interval (default 5s):
|
|
|
|
```
|
|
eBPF packet -> FlowTracker (per-flow stats)
|
|
-> FlowFeatures (extract + normalize ~80 features)
|
|
-> Inference (sliding window buffer per src_ip)
|
|
-> ort Session (LSTM autoencoder, MSE score)
|
|
-> AttackAggregator -> MLAlert (WebSocket)
|
|
```
|
|
|
|
Inference engine: ort-tract (pure Rust, no native ORT binary required).
|
|
`ort::set_api(ort_tract::api())` must be called before any Session is created.
|
|
Session is wrapped in `Mutex<Session>` because `Session::run` requires `&mut self`.
|
|
|
|
## Config
|
|
|
|
Runtime config: `config.toml`
|
|
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://<host>:8080`
|
|
|
|
All routes that require auth expect `Authorization: Bearer <token>` 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":"<jwt>"}` |
|
|
| 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<AppState>` 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<AppState>,
|
|
) -> impl IntoResponse {
|
|
// user.0.role, user.0.username available
|
|
}
|
|
```
|
|
|
|
## Implementation Priority
|
|
|
|
See `TODO` for the full backlog.
|