ExfilGuardian
Backend

API

gRPC agent ingestion and REST dashboard endpoints

Overview

The exfil-server crate runs two concurrent servers:

ServerFrameworkPortPurpose
gRPCTonic50051Agent packet ingestion via Protobuf streaming
RESTAxum8080Dashboard status, alerts, metrics, agent enrollment

Both servers are started concurrently via tokio::select! and share the same ServerPipeline instance. Configuration is loaded from config.yaml at startup with sensible defaults.

Running

cargo run -p exfil-server

The server reads config.yaml for addresses and analysis tuning. If the file is missing, all defaults are used.

gRPC Server (Tonic, port 50051)

The gRPC server handles agent-to-server communication as defined in proto/exfil.proto.

Service Definition

service ExfilService {
  rpc StreamPackets(stream NetworkPacket) returns (StreamResponse);
}

The StreamPackets RPC is a client-streaming call: the agent opens a persistent connection and sends a continuous stream of NetworkPacket messages. The server responds with a single StreamResponse when the stream closes.

Implementation

The gRPC receiver (server/src/grpc/receiver.rs) implements ExfilService:

  1. Accepts the incoming stream from the agent
  2. Deserializes each NetworkPacket from Protobuf
  3. Converts to analysis PacketMetadata via the bridge module (server/src/core/bridge.rs)
  4. Ingests into FlowTracker for flow aggregation
  5. Expired flows are processed through the full AnalysisPipeline (DPI + Signatures + Behavioral + Correlation)
  6. On client disconnect, all remaining active flows are flushed and analyzed

Bridge Module

The bridge (server/src/core/bridge.rs) converts gRPC types to analysis types:

  • local_ip / remote_ip (u32) to IpAddr
  • timestamp (ms) to timestamp_ns (ns)
  • protocol (u32) to TransportProtocol::from_ip_protocol()
  • DNS metadata extraction from port 53 payloads
  • ICMP type/code/payload extraction
  • UDP header stripping (WFP outbound includes it, inbound doesn't)
  • Flow ID computation from 5-tuple hash

Server Pipeline

The ServerPipeline (server/src/core/pipeline.rs) owns:

  • A FlowTracker (from the analysis crate) wrapped in parking_lot::Mutex
  • The analysis AnalysisPipeline (DPI, signatures, behavioral, correlation)
  • An alert channel for downstream processing

The lock is always dropped before any .await to prevent deadlocks.

TLS / mTLS Configuration

The server generates certificates at startup via rcgen if they do not already exist:

  • certs_rcgen/ca.pem / ca.key: Root CA (ECDSA P-256)
  • certs_rcgen/server.pem / server.key: Server certificate (signed by CA)

mTLS is configured but currently disabled for development:

let tls = tonic::transport::ServerTlsConfig::new()
    .identity(identity)
    .client_ca_root(client_ca); // mTLS: verify agent certificates

let grpc_server = Server::builder()
    //.tls_config(tls)?  // Temporarily disabled for debugging
    .add_service(ExfilServiceServer::new(grpc_service))
    .serve(grpc_addr);

When re-enabled, agents must present a valid client certificate signed by the server's CA to connect.

REST Server (Axum, port 8080)

The REST API serves the dashboard frontend, static files (driver binaries, agent executables), the agent enrollment workflow, and the detection pipeline's alerts and metrics.

Endpoints

The REST API drives every dashboard page — no endpoint leaves a screen empty.

Status & metrics

MethodPathDescription
GET/api/statusServer health check and version
GET/api/v1/metricsPipeline metrics (packets, flows, detections, alerts)
GET/api/v1/metrics/historyBounded ring of recent metric snapshots (last 120)

Alerts

MethodPathDescription
GET/api/v1/alertsList active alerts from the correlation engine
GET/api/v1/alerts/{id}Fetch a single alert
DELETE/api/v1/alerts/{id}Remove an alert
POST/api/v1/alerts/{id}/statusUpdate an alert's triage status

Flows & agents

MethodPathDescription
GET/api/v1/flowsList flows currently being aggregated
GET/api/v1/flows/{id}Fetch a single flow record
GET/api/v1/agentsList known agents (id, status, packet counters)

Each flow carries an agent_id — a stable UUIDv5 derived from the agent's connection IP — so the dashboard can filter flows and alerts per agent.

Signatures (YAML rules)

MethodPathDescription
GET/api/v1/signaturesList loaded signature rules
POST/api/v1/signaturesCreate or update a rule (validated, then hot-reloaded)
POST/api/v1/signatures/validateDry-run validation of a rule without writing it
POST/api/v1/signatures/{id}/toggleEnable / disable a rule
POST/api/v1/signatures/reloadForce a reload of all rules from disk

A notify-based filesystem watcher hot-reloads the signatures/ directory on any change (500 ms debounce), so edits land without a server restart.

Whitelist (exclusions)

MethodPathDescription
GET/api/v1/whitelistList exclusion entries
POST/api/v1/whitelistAdd an exclusion
DELETE/api/v1/whitelist/{id}Remove an exclusion

Every exclusion is scoped to a protocol (dns, http, tls, icmp, tcp, udp, or any): whitelisting a DNS resolver as dns suppresses its DNS noise without blinding the engine to a TLS C2 channel toward the same IP.

Config & enrollment

MethodPathDescription
GET/api/v1/configCurrent server analysis config
POST/api/v1/configReplace the in-memory analysis config
POST/api/v1/agent/tokensGenerate a single-use enrollment token
GET/api/v1/enroll/windowsDownload the auto-deploy PowerShell script
GET/static/*Serve static files (agent binary, driver, certs)

Alert Response

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "category": "DnsTunneling",
    "confidence": 0.82,
    "severity": "High",
    "summary": "DNS tunneling detected: high entropy subdomains to evil.com"
  }
]

Metrics Response

{
  "packets_received": 142857,
  "active_flows": 23,
  "flows_processed": 4521,
  "detections_generated": 17,
  "alerts_generated": 3,
  "active_alerts": 2,
  "signature_rules": 12
}

Status Response

{
  "status": "Running",
  "version": "0.3.0-analysis"
}

Enrollment Flow

Agent enrollment follows a Teleport-style workflow: the admin generates a single-use token on the server, which produces a one-liner PowerShell command that fully deploys the agent on a target endpoint.

Step 1: Generate Token (Admin)

curl -X POST http://server:8080/api/v1/agent/tokens

Response:

{
  "token": "a1b2c3d4-...",
  "command": "Invoke-RestMethod -Uri \"http://server:8080/api/v1/enroll/windows?token=a1b2c3d4-...\" | Invoke-Expression"
}

The token is a UUID v4 stored in an in-memory HashSet<String> and is consumed on first use (single-use).

Step 2: Run on Target Endpoint (One-Liner)

The admin copies the command value and runs it in an elevated PowerShell session on the target Windows machine. The returned script performs:

  1. Create directories: C:\Program Files\ExfilGuardian\ and certs\
  2. Write mTLS certificates: The server dynamically generates a unique agent certificate signed by its CA and embeds the PEM files (base64-encoded) directly in the script
  3. Download binaries: Fetches exfil-agent.exe, exfil_guardian.sys, .inf, .cat, and driver.cer from /static
  4. Install driver certificate: Imports the driver's test signing certificate into Cert:\LocalMachine\Root and Cert:\LocalMachine\TrustedPublisher
  5. Install driver package: pnputil.exe /add-driver exfil_guardian.inf /install
  6. Configure registry: Sets HKLM\SOFTWARE\ExfilGuardian\TargetUrl to https://<server_ip>:50051
  7. Create Windows services:
    • ExfilGuardian (kernel, demand-start): the WFP driver
    • ExfilAgent (user-mode, auto-start): the gRPC agent
  8. Start both services: Driver first, then agent after a 2-second delay

Agent Certificate Generation

Each enrollment generates a unique client certificate:

let mut client_params = CertificateParams::default();
client_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
let mut client_dn = DistinguishedName::new();
client_dn.push(DnType::CommonName, format!("exfil-agent-{}", &token[0..6]));

let client_cert = Certificate::from_params(client_params)?;
let agent_cert_pem = client_cert.serialize_pem_with_signer(&ca_cert)?;

The certificate's Common Name includes the first 6 characters of the enrollment token for identification. The CA private key is loaded from certs_rcgen/ca.key to sign each agent certificate.

Architecture Diagram

On this page