Enrollment Flow
Introduction
Enrollment is the one-time process where a sensor agent registers itself with the backend, exchanging a temporary install token for a durable sensor token. This document describes the complete enrollment lifecycle, from token generation in the frontend to persistent token storage on the sensor host.
Enrollment Lifecycle
Enrollment Request Payload
The agent sends the following data to the backend during enrollment:
{
"installToken": "one-time-token-from-frontend",
"hostname": "sensor-ubuntu-host",
"osInfo": "Ubuntu 24.04 LTS",
"architecture": "x86_64",
"agentVersion": "2.0.0",
"sensorIp": "192.168.1.100",
"sensorPort": 22,
"sudoUser": "sensoradmin",
"interfaces": [
{
"name": "enp130s0f0",
"isManagement": true,
"isLoopback": false,
"isVirtual": false,
"isWireless": false,
"isUp": true,
"bytesSent": 1073741824,
"bytesRecv": 2147483648
},
{
"name": "enp130s0f1",
"isManagement": false,
"isLoopback": false,
"isVirtual": false,
"isWireless": false,
"isUp": true,
"bytesSent": 0,
"bytesRecv": 0
}
]
}
Field Details
| Field | Source | Description |
|---|---|---|
installToken | INSTALL_TOKEN env var | One-time token generated by frontend |
hostname | /host-hostname or os.Hostname() | Host machine hostname, not container hostname |
osInfo | /host-os-release PRETTY_NAME | Host OS name (e.g., "Ubuntu 24.04 LTS") |
architecture | runtime.GOARCH | CPU architecture, mapped to x86_64 / arm64 |
agentVersion | Constant 2.0.0 | Agent software version |
sensorIp | UDP dial to 8.8.8.8:80 | Primary outbound IP of the host |
sensorPort | SENSOR_PORT env var (default: 22) | SSH port for Ansible access |
sudoUser | Hardcoded sensoradmin | User for privilege escalation |
interfaces | net.Interfaces() + gopsutil | All network interfaces with classification |
Enrollment Response
{
"success": true,
"data": {
"sensorId": "uuid",
"sensorToken": "durable-token-for-heartbeats",
"backendUrl": "http://10.10.10.70:5009",
"heartbeatIntervalSeconds": 30
}
}
The agent extracts sensorToken and heartbeatIntervalSeconds from the response.
Token Persistence
After successful enrollment, persistSensorToken() writes the durable token to the config file (default: /etc/ravenxcope/sensor-agent.env):
Before enrollment:
SENSOR_ID=sensor-1
INSTALL_TOKEN=install-token
BACKEND_URL=https://backend.local
After enrollment:
SENSOR_ID=sensor-1
# INSTALL_TOKEN consumed during enrollment
BACKEND_URL=https://backend.local
SENSOR_TOKEN=durable-token
Key behaviors:
- Creates parent directories if they don't exist (
os.MkdirAll). - If
SENSOR_TOKEN=already exists, replaces the value in-place. - If
INSTALL_TOKEN=exists, comments it out as consumed. - File permissions are set to
0600(owner read/write only). - If persistence fails, the agent logs a warning but continues running.
Host Metadata Detection
The agent detects host metadata using multiple strategies to work correctly inside Docker containers:
Hostname
- Read from bind-mounted file at
HOST_HOSTNAME_FILEenv var (default:/host-hostname) - Fall back to
os.Hostname() - Last resort: return
"unknown"
OS Info
- Read
PRETTY_NAMEfromHOST_OS_RELEASE_FILEenv var (default:/host-os-release) - Fall back to
/etc/os-releaseinside the container - Last resort: return
runtime.GOOS(e.g.,"linux")
Primary IP
Uses a UDP dial to 8.8.8.8:80 (no actual traffic sent) to determine the local IP address of the default route interface.
Architecture
Maps runtime.GOARCH to human-readable names:
amd64→x86_64arm64→arm64
Failure Modes
| Scenario | Behavior |
|---|---|
INSTALL_TOKEN missing on first run | Agent exits with fatal error |
| Backend unreachable during enrollment | Agent exits with fatal error |
| Backend returns non-200 status | Agent exits with fatal error |
| Token persistence fails | Warning logged, agent continues with in-memory token |
Agent restarts with persisted SENSOR_TOKEN | Enrollment is skipped entirely |