Add-on authors
Native add-ons
Author, build, sign, and ship native ServiceRadar agent add-ons — compiled Go/Rust binaries the agent supervises out-of-process, with the addon.yaml manifest, go-plugin protocol, and signed-bundle pipeline.
A native add-on is a compiled Go or Rust binary the ServiceRadar agent supervises at runtime. Unlike a WebAssembly plugin, which runs sandboxed and in-process, a native add-on runs as its own OS process. That makes it the right tool for work a WASM sandbox cannot do: eBPF, raw packet capture, container-runtime access, and other privileged or heavy workloads.
Native add-ons share the manifest, signing, and bundling infrastructure of plugins, but differ in how they are delivered to the agent and how they are supervised.
Native add-on vs. WebAssembly plugin
| Native add-on | WASM plugin | |
|---|---|---|
| Execution | Separate OS process | In-process WASM module |
| Language | Compiled Go or Rust | Any language compiled to WASM |
| Isolation | OS user, capabilities, systemd hardening | WASM sandbox + capability allowlist |
| Lifecycle | Launched, health-checked, restarted, stopped | Invoked per check run |
| Use it for | eBPF, packet capture, CRI access, daemons | Checks, metrics, inventory, events |
The add-on catalog
ServiceRadar ships these first-party add-ons. Each has its own reference page.
| Add-on | Language | Delivery | Supervision | Purpose |
|---|---|---|---|---|
| Sample | Go | pushed-artifact | agent-sidecar | Reference go-plugin add-on |
| Rust sample | Rust | pushed-artifact | agent-sidecar | Rust reference add-on |
| PowerDNS | Rust | pushed-artifact | agent-sidecar | RPZ → OCSF DNS Activity telemetry |
| netprobe | Rust | pushed-artifact | systemd-service | eBPF host-network visibility |
| Workload identity | Rust | pushed-artifact | systemd-service | CRI/container metadata collector |
| Endpoint inventory | Go | pushed-artifact | systemd-timer | CycloneDX software SBOM |
| Bumblebee scan | Go | pushed-artifact | systemd-timer | Exposure scanner |
| RDP adapter | Rust | pushed-artifact | ephemeral-helper | Per-session RDP helper |
| Remote access | Go | compiled-in | config-toggle | In-agent remote access suite |
Package layout
An add-on’s manifest package lives under addons/<id>/. The binary itself is
built from the main codebase (go/cmd/... or rust/...).
addons/<id>/
├── addon.yaml # Required: the manifest
├── config.schema.json # Required: JSON Schema for operator config
├── BUILD.bazel # Exposes manifest/config/units to the bundle build
├── README.md # Optional
├── serviceradar-<id>.service # Optional: systemd unit (systemd supervision)
├── serviceradar-<id>.timer # Optional: systemd timer
├── schemas/ # Optional: telemetry payload JSON Schemas
│ └── <signal>.schema.json
└── display/ # Optional: telemetry display contracts
└── <signal>.display.jsonThe addon.yaml manifest
The manifest is authored as YAML and validated (after YAML→JSON normalization)
against addons/native-addon-manifest.schema.json before the signed bundle is
assembled. The build fails closed on a manifest that is missing required
fields or declares an unknown kind / delivery / supervision value.
id: sample
name: Sample Add-on
version: 0.1.0
description: Reference native add-on that validates the go-plugin add-on contract.
kind: native
delivery: pushed-artifact # compiled-in | pushed-artifact | os-package
supervision: agent-sidecar # config-toggle | agent-sidecar | systemd-service | systemd-timer | ephemeral-helper
language: go # go | rust
capabilities:
- sample
requires:
base_agent: ">=1.2.0"
platforms: [linux]
os_capabilities: []
run_as: serviceradar
plugin:
protocol: grpc
app_protocol_version: 1
exec:
binary: serviceradar-sample-addon
install_path: /usr/local/lib/serviceradar/bin
config_schema: config.schema.jsonRequired fields
| Field | Description |
|---|---|
id |
Stable lowercase identifier (^[a-z0-9][a-z0-9_-]*$). |
name |
Human-readable name. |
version |
Semantic version (e.g. 0.1.0). |
kind |
Always native for this schema. |
delivery |
How the artifact reaches the agent (see below). |
supervision |
How the agent supervises it at runtime (see below). |
capabilities |
One or more capability strings the add-on provides. |
requires |
Runtime requirements (base_agent, platforms, …). |
exec |
Execution descriptor (binary, install_path, optional args). |
config_schema |
Relative path to the config JSON Schema in the bundle. |
Optional fields: description, language (go | rust), plugin (go-plugin
settings), state_dirs, signal_schemas, and artifacts (populated by the
signed-bundle build, omitted in source manifests).
Delivery models
-
pushed-artifact— the agent fetches a signed per-architecture artifact from object storage, verifies it, and stages it. The model for most add-ons. -
compiled-in— the feature already lives in the base agent binary; no separate artifact is built or pushed. -
os-package— delivered through an OS package manager.
Supervision models
-
agent-sidecar— the agent supervises the add-on over the go-plugin protocol: launch, configure, health-check, restart with backoff. Requires aplugin:block. -
systemd-service— a long-running daemon supervised by systemd; the bundle ships a.serviceunit the agent installs. -
systemd-timer— a periodic one-shot supervised by a systemd timer; the bundle ships.service+.timerunits. -
ephemeral-helper— staged but not run as a long-lived process; invoked on demand by another component (for example, per-session helpers). -
config-toggle— a compiled-in feature enabled by configuration.
Requirements and security
requires:
base_agent: ">=1.2.0"
platforms: [linux] # linux | darwin | windows
os_capabilities: [CAP_NET_RAW, CAP_NET_ADMIN, CAP_BPF, CAP_PERFMON]
run_as: serviceradar # or root-
os_capabilitiesare normalized and allowlist-checked by the agent, then applied to the staged binary by the root-owned agent-updater viasetcap— never by the add-on itself. -
run_asis the OS user the add-on runs as. Privileged collectors (CRI, exposure scanning) declareroot; sidecars default toserviceradar. -
state_dirsenumerate the absolute directories the add-on may read and write at runtime. They document the add-on’s footprint and back future sandbox enforcement.
Signal schemas
Add-ons that emit logs or events declare their payload schemas and display contracts so the platform can validate and render them. Each entry pairs an OCSF or OTEL payload schema with a display contract:
signal_schemas:
- id: com.carverauto.powerdns.dns_activity
version: 1.0.0
signal_type: event # event | log
payload_kind: ocsf_event # ocsf_event | otel_log
payload_schema: schemas/dns_activity.schema.json
display_contract: display/dns_activity.display.json
display_contract_id: com.carverauto.powerdns.dns_activity.display
display_contract_version: 1.0.0
ocsf_schema_version: 1.5.0
class_uid: 4003The go-plugin protocol
agent-sidecar add-ons are supervised by the agent’s HashiCorp go-plugin
client over gRPC on a restricted Unix-domain socket with mutual TLS. The agent is
the client; the add-on is the server.
-
Handshake — the agent sets the magic cookie
SERVICERADAR_ADDON_PLUGIN=serviceradar-addon-v1and the add-on binds a Unix socket, then prints a single handshake line advertising the core/app protocol versions, the socket path,grpc, and its base64 server-certificate DER. -
app_protocol_versionis pinned (currently1) in both the manifest’splugin:block and the SDK, so protocol drift is caught at the handshake rather than silently. - AutoMTLS — agent and add-on exchange self-signed ECDSA P-521 certificates over environment variables and pin each peer by exact certificate bytes.
-
gRPC services — the add-on serves
serviceradar.agent.addon.v1.AddonService(Info,Configure,Health, and optionalStreamTelemetry) plus the standard gRPC health service.
The agent’s add-on manager owns the full lifecycle: reconciling assignments, launching and configuring add-ons, polling health, restarting with backoff and a circuit breaker, and reporting per-add-on status.
Building a Rust agent-sidecar add-on
The addon-sdk crate (rust/addon-sdk) implements the handshake, AutoMTLS, and
gRPC plumbing. You implement the Addon trait and call serve:
use addon_sdk::{serve, Addon, ConfigureResult, Health, Info};
use async_trait::async_trait;
struct MyAddon;
#[async_trait]
impl Addon for MyAddon {
async fn info(&self) -> anyhow::Result<Info> {
Ok(Info {
id: "my-addon".into(),
version: "0.1.0".into(),
capabilities: vec!["my-cap".into()],
})
}
async fn configure(&self, config_json: &[u8]) -> anyhow::Result<ConfigureResult> {
Ok(ConfigureResult { config_hash: "...".into(), accepted: true, error: String::new() })
}
async fn health(&self) -> anyhow::Result<Health> {
Ok(Health::default())
}
}
#[tokio::main]
async fn main() {
if let Err(e) = serve(MyAddon).await {
eprintln!("{e}");
std::process::exit(1);
}
}
Keep the id, version, and capabilities the binary reports from info() in
sync with the manifest so the runtime and the manifest agree. Add-ons that stream
telemetry implement stream_telemetry, declare the
native-telemetry:v1 capability, and build batches with the SDK’s
TelemetryBatchBuilder, ocsf_event_record, and attach_signal_schema_ref
helpers. Go add-ons use the equivalent go/pkg/addon SDK and sdk.Serve().
Build, sign, and publish
The signed-bundle pipeline lives under build/native_addons/ and is driven by
Bazel.
-
Build —
declare_native_addon_targets()cross-compiles per-architecture binaries andassemble_addon_bundle.pyproduces a deterministic ZIP bundle (manifest, config schema, per-arch binaries, optional systemd units), a SHA256 file, and ametadata.jsonwith per-arch artifact records. -
Sign —
addon-artifact-signature-toolsigns each per-arch artifact with ed25519, reusing the agent release trust root (SERVICERADAR_AGENT_RELEASE_PRIVATE_KEY/…_PUBLIC_KEY). -
Publish —
publish_addon.shpushes the bundle, tarballs, metadata, and signatures to an OCI artifact registry via ORAS for control-plane ingestion.
Install and activation
When the agent receives an assignment for a pushed-artifact add-on, it:
- fetches the signed artifact from object storage,
- verifies its SHA256 against the assignment,
- verifies the ed25519 signature with the agent release public key, and
-
stages the artifact under a versioned directory with an atomic
currentsymlink — the runtime layout is<release-runtime-root>/addons/<id>/current/(for example/var/lib/serviceradar/agent/addons/<id>/current/), resolved by the agent’s activation runtime rather than the manifest’s declaredinstall_path.
For systemd-service and systemd-timer add-ons, the agent installs the bundled
units via the root-owned agent-updater and enables them through systemctl. The
units run under the serviceradar.slice cgroup with systemd hardening
(ProtectSystem=strict, NoNewPrivileges, a tuned SystemCallFilter, and only
the capabilities the workload requires).