Sensors and Virtual Sensors
Introduction
This module handles physical sensor registration, one-time host enrollment, interface inventory management, host heartbeat and metrics ingestion, and virtual sensor lifecycle management. A physical sensor is the hardware host, while a virtual sensor is a deployable Suricata workload that consumes two eligible interfaces on that host.
The sensor orchestration has been split into focused services:
SensorsService— Core CRUD and sensor management (480 lines)SensorInterfaceAllocationService— Network interface pool validationResilientHttpService— Circuit breaker + retry for external HTTP callsVirtualSensorsService— Virtual sensor lifecycle managementSensorHeartbeatService— Heartbeat processing
All controllers delegate to service interfaces. Repositories (SensorRepository, SensorHeartbeatRepository, VirtualSensorRepository) handle data access.
Controllers
| Controller | Route | Description |
|---|---|---|
SensorsController | api/sensors | Sensor CRUD and lifecycle |
VirtualSensorsController | api/virtual-sensors | Virtual sensor operations |
SensorHeartbeatController | api/sensor-heartbeat | Heartbeat processing |
Files:
API/Controllers/Sensors/SensorsController.csAPI/Controllers/Sensors/VirtualSensorsController.csAPI/Controllers/Sensors/SensorHeartbeatController.cs
Sensor Entity
File: Domain/Entities/Sensor.cs
Table: sensors
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
name | varchar(255) | Required | Sensor display name |
organization_id | uuid | FK, Required | Owning organization |
status | varchar(50) | Required | unknown, off, on |
operational_status | varchar(50) | active, inactive, maintenance | |
sensor_ip | varchar(50) | Nullable | Sensor IP address |
sensor_port | int | Nullable | Sensor port number |
sudo_user | varchar(100) | Nullable | SSH sudo user for deployment |
hostname | varchar(255) | Nullable | Hostname reported by sensor agent |
os_info | varchar(255) | Nullable | Operating system information |
architecture | varchar(100) | Nullable | Reported CPU architecture |
agent_version | varchar(100) | Nullable | Installed host agent version |
enrollment_status | varchar(50) | Required | pending, enrolled |
management_interface | varchar(255) | Nullable | Management network interface |
last_heartbeat_at | timestamp | Nullable | Last agent heartbeat received |
last_inventory_at | timestamp | Nullable | Last interface inventory update |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Navigation Properties
[ForeignKey("OrganizationId")]
public Organization Organization { get; set; } = null!;
public ICollection<SensorHeartbeat> Heartbeats { get; set; }
public ICollection<VirtualSensor> VirtualSensors { get; set; }
public ICollection<SensorNetworkInterface> Interfaces { get; set; }
Status Model
Sensors have two status dimensions:
Status (connectivity):
| Value | Meaning |
|---|---|
unknown | Initial state, never connected |
off | Sensor is offline |
on | Sensor is online and reporting |
Operational Status (operational):
| Value | Meaning |
|---|---|
active | Normal operation |
inactive | Disabled by administrator |
maintenance | Under maintenance |
Virtual Sensor Entity
File: Domain/Entities/VirtualSensor.cs
Table: virtual_sensors
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
sensor_id | uuid | FK, Required | Parent sensor |
name | varchar(255) | Required | Virtual sensor name |
interface1 | varchar(255) | Required | First network interface |
interface2 | varchar(255) | Required | Second network interface |
home_net | varchar(255) | Home network CIDR (default: 192.168.0.0/16) | |
status | varchar(50) | Required | Lifecycle status |
activation_status | varchar(50) | Nullable | Activation status reported back over NATS |
activation_error | text | Nullable | Error message from failed activation |
execution_id | varchar(255) | Nullable | Correlation ID for the activation command |
deployment_path | varchar(255) | Nullable | Deployment target path |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Virtual Sensor Status Model
Status (lifecycle):
| Value | Meaning |
|---|---|
inactive | Created but not yet activated |
activating | Activation command sent, awaiting sensor confirmation |
active | Successfully activated and running |
failed | Activation failed |
Activation Status (reported by the sensor over NATS):
| Value | Meaning |
|---|---|
pending | Queued for activation |
running | Sensor is applying the command |
success | Activation completed successfully |
failed | Activation failed (see error) |
Sensor Heartbeat Entity
File: Domain/Entities/SensorHeartbeat.cs
Table: sensor_heartbeats
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
sensor_id | uuid | FK, Required | Reporting sensor |
last_seen | timestamp | Nullable | Last seen timestamp |
isActive | bool | Nullable | Active flag |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Sensor Responsibilities
Registration and Enrollment
- Create a sensor draft with name and organization
- Issue a one-time claim code the operator pastes into
ravenxcope-sensor enroll --code(an install-token/install-script path also exists for scripted installs) - The sensor redeems the claim, submits a CSR, and receives an mTLS client certificate (see Sensor → Enrollment & PKI)
- Persist host metadata plus a structured interface inventory on enrollment
Activation Orchestration
- Activate or deactivate virtual sensors by publishing NATS control commands (
NatsControlPublisher) - Track activation progress via status messages the sensor publishes back over NATS (
VirtualSensorStatusConsumer) - The sensor launches Suricata locally for each virtual sensor (see Backend Control-Plane Integrations)
Heartbeat Processing
- Receive heartbeat reports from the host agent
- Update
last_heartbeat_atandlast_inventory_aton sensor - Store online status in InfluxDB
sensor_status - Store host CPU, memory, and eligible interface traffic in InfluxDB
sensor_metrics
Virtual Sensor Responsibilities
Lifecycle Management
- Create virtual sensors attached to a physical sensor
- Configure interface pairs (interface1, interface2) and home network
- Track deployment path and execution state
Activation via NATS commands
- Build an
activate/deactivatecommand (VirtualSensorsService.BuildCommand) and publish it torxc.<tenant>.<sensor>.cmdviaNatsControlPublisher - The command payload carries the runtime configuration the sensor needs:
- Interface assignments
- InfluxDB connection details
- Data collector endpoint
- Docker registry credentials (required before activation)
- Backend URL for callbacks
- Selected curated Suricata rule filenames
- Canonical Suricata variables for the selected rules
Interface Allocation
- Interfaces are stored as structured records on the parent sensor
- Virtual sensor creation requires two explicit interfaces
- Only interfaces marked
isEligible=truecan be assigned - Assignment state is tracked on the interface record itself
External Dependencies
| Dependency | Config Key | Purpose |
|---|---|---|
| NATS Provisioner | NatsProvisioner:* | Account/user credential issuance |
| NATS | NatsMessaging:* | Control commands + status |
| Sensor API Key | SensorApiSettings:ApiKey | Sensor trigger authentication |
| Backend URL | BackendUrl | Callback URL for sensors |
| Data Collector | DataCollector:Endpoint/Port | Sensor data destination |
| Docker Registry | DockerRegistry:Registry/Username/Password | Container image source |
| InfluxDB | InfluxDb:* | Metrics and heartbeat streams |
The curated Suricata rules catalog is now served directly by the backend — Ansible is no longer involved:
- Backend route:
GET /api/suricata-rules - Source:
SuricataRulesCatalogServicereads*.rulesfiles fromResources/suricata-rulesunder the backend content root - Selected rule filenames are validated against this catalog before being included in an activation command
These settings are aggregated into SensorRuntimeOptions for convenience:
public sealed class SensorRuntimeOptions
{
public string BackendUrl { get; set; }
public string SensorApiKey { get; set; }
public string InfluxDbUrl { get; set; }
public string InfluxDbToken { get; set; }
public string InfluxDbOrg { get; set; }
public string InfluxDbBucket { get; set; }
public string DataCollectorEndpoint { get; set; }
public string DataCollectorPort { get; set; }
public string DockerRegistry { get; set; }
public string DockerUsername { get; set; }
public string DockerPassword { get; set; }
}
gRPC Services
Two mTLS gRPC services are mapped in Extensions/WebApplicationExtensions.cs:
app.MapGrpcService<SensorHealthcheckService>();
app.MapGrpcService<SensorEventStreamService>();
SensorHealthcheckService(Protos/sensor_healthcheck.proto) — sensors report health status.SensorEventStreamService(Protos/sensor_event.proto) — the target mTLS alert-ingestion path; today it authenticates the client cert and enforces liveness rather than persisting events.
Both run behind SensorMtlsInterceptor. See Backend Control-Plane Integrations for how these fit the wider control plane.
Cascade Delete Rules
| Parent → Child | Behavior |
|---|---|
| Organization → Sensor | Cascade |
| Sensor → SensorHeartbeat | Cascade |
| Sensor → VirtualSensor | Cascade |
Key Risk Areas
| Area | Risk |
|---|---|
| Activation orchestration | Multi-step flow relies on NATS command/status contracts; status updates are fire-and-forget |
| No integration tests | Critical activation paths lack test coverage |
| Credential lifecycle | Sensor connectivity depends on the NATS Provisioner being reachable when accounts/users are issued |
Refactoring Status
Completed
- Split sensor orchestration service into focused services.
- Added dedicated
VirtualSensorsServicelifecycle handling. - Added resilient outbound execution path for external calls (e.g. the NATS Provisioner).
- Moved interface allocation logic into dedicated service.
- Kept controllers focused on transport and delegation.
Open
- Add integration tests for activation and rollback flows.