Linux Host Agent
The Stella Ops Linux host agent is the RuntimeInstrumentation host service for Linux container estates. It shares StellaOps.Agent.Core contracts with the Windows agent and uses optional Linux sensors only as inputs.
Scope
- Project:
src/RuntimeInstrumentation/StellaOps.Agent.Linux/ - Tests:
src/RuntimeInstrumentation/StellaOps.Agent.Linux.Tests/ - Service model: generic-host daemon or supervised process.
- Runtime status baseline: file-backed snapshots and bounded local commands.
- Evidence posture: digest-gated Agent.Core evidence publication through
IRuntimeEvidencePublisher. WhenRuntimeInstrumentation:RuntimeFacts:BaseAddressis set,LinuxRuntimeFactsEvidencePublisherposts symbolicated runtime facts to Signals (POST /signals/runtime-facts/ndjson); otherwise the default no-op publisher returns a retry result so entries remain in the bounded spool. - Packaging: the project is an executable; this repository produces no package, installer, or container image for it.
- Optional sensor:
StellaOps.Agent.Tetragon, Linux-only and disabled unless explicitly configured. - Out of scope: Kubernetes, Helm, DaemonSets, Kubernetes API watches, and Kubernetes-first runtime collection.
Installation
Run the Linux agent as a supervised host process under the distribution’s service manager. No Stella Ops package or offline-kit artifact for the agent is built in this repository. The agent must run on the host that owns the container runtime status source.
The release package must not install Kubernetes manifests, Helm charts, or DaemonSets for RuntimeInstrumentation.
Configuration
Configuration precedence is:
- Local JSON files:
/etc/stellaops/linux-agent.json, thenlinux-agent.json. - Environment variables prefixed with
STELLAOPS_LINUX_AGENT_(__maps to:). - Optional policy-delivered JSON file referenced by
RuntimeInstrumentation:LinuxAgent:PolicyFile.
Important settings:
RuntimeInstrumentation:LinuxAgent:AgentIdRuntimeInstrumentation:LinuxAgent:HostIdRuntimeInstrumentation:LinuxAgent:InstallIdRuntimeInstrumentation:LinuxAgent:EnvironmentRuntimeInstrumentation:LinuxAgent:CollectionIntervalRuntimeInstrumentation:LinuxAgent:MaxRuntimeStatusAgeRuntimeInstrumentation:LinuxAgent:MaxObservationsPerCycleRuntimeInstrumentation:LinuxAgent:RuntimeStatus:FilesRuntimeInstrumentation:LinuxAgent:RuntimeStatus:CommandsRuntimeInstrumentation:LinuxAgent:TetragonBridge:EnabledTetragon:ExportClient:AddressRuntimeInstrumentation:LinuxAgent:SymbolResolver:*RuntimeInstrumentation:RuntimeFacts:BaseAddress(Signals base address; empty keeps the no-op publisher)RuntimeInstrumentation:RuntimeFacts:CallgraphIdandRuntimeInstrumentation:RuntimeFacts:CallgraphIdByImageDigest(required by the Signals endpoint; without one, envelopes are retried withruntime_facts.callgraph_id_not_configured)RuntimeInstrumentation:RuntimeFacts:CompressPayloadRuntimeInstrumentation:RuntimeFacts:Authority:{Url,TokenEndpoint,ClientId,ClientSecret,Tenant}(required whenBaseAddressis set; the agent refuses to start withoutUrl,ClientIdandClientSecret). The publisher mints a client-credentials token with scopesignals:writeand sends it as a Bearer header. Register a confidential Authority client allowedsignals:writefor the agent and supply the secret through the environment (RuntimeInstrumentation__RuntimeFacts__Authority__ClientSecret).
Runtime status commands must use fully qualified executable paths, typed arguments, no shell invocation, timeout bounds, and output byte limits.
Runtime Status
Publishable runtime status requires an immutable sha256:<64-hex> image digest. Missing, malformed, tag-only, stale, ambiguous, timed-out, permission-denied, and over-large status inputs fail closed before evidence publication.
File-backed and command-backed local sources are the active baseline. Native CRI/containerd/Docker/Podman APIs remain inactive until contracts, fixtures, permissions, dependencies, and tests are approved.
Symbol resolution and source-line attribution
StellaOps.Agent.Linux.SymbolResolver upgrades runtime observations by reading /proc/<pid>/maps plus the backing ELF binary’s .symtab / .dynsym and, best-effort, its uncompressed DWARF 4/5 .debug_line section. When debug-info is present, the resolved frame carries Module + Symbol + Offset + Line; when debug-info is missing, stripped, or stored under SHF_COMPRESSED, the frame degrades gracefully to full-symbols (Line null) or partial-symbols / addresses-only (per StackCaptureModes). Production images that strip debug-info surface full-symbols frames with a null Line rather than failing resolution. The DWARF reader is intentionally scoped to .debug_line only — DIE walking (.debug_info), type tables, and inline-function attribution are out of scope; line attribution depends on the toolchain emitting an uncompressed line-number program (gcc -gdwarf-4/-gdwarf-5 without -gz=zlib).
Optional Tetragon
Tetragon is not the Linux agent. It is an optional Linux eBPF sensor bridge that can provide RuntimeObservation records to the Linux host agent.
Enable it only when the host has a supported local Tetragon export endpoint:
RuntimeInstrumentation:LinuxAgent:TetragonBridge:Enabled=true
Tetragon:ExportClient:Address=http://127.0.0.1:<port>
Enabling the bridge also registers the symbol resolver and its observation filter; with the bridge disabled, neither is wired.
The bridge never publishes evidence directly. The Linux host agent applies the normal runtime digest gate and Agent.Core publisher path.
Transport gap. TetragonExportClient speaks HTTP: it reads /v1/health, /v1/policies, and an NDJSON event stream at /v1/events under Tetragon:ExportClient:Address. The Tetragon server started below listens on gRPC (--server-address), and the module contains no gRPC client. Pointing the export client directly at the Tetragon gRPC port does not produce observations; an HTTP/NDJSON export endpoint in front of Tetragon is required, and none is shipped in this repository.
Installing the Stella-vendored Tetragon OCI image
Stella Ops vendors a license-verified Tetragon image as stellaops/tetragon (Apache-2.0). See devops/release/components.json vendoredUpstream for the pinned version and digest, devops/airgap/tetragon/Dockerfile for the wrapper image, and third-party-licenses/tetragon-apache-2.0.txt for the license. The commands below use <registry>/stellaops/tetragon:<version>; substitute your registry and pin by digest.
Install via docker (privileged; Tetragon needs kernel capabilities for eBPF). Stage the tracing policy in a host-writable directory FIRST so it is mounted read-only at boot (Tetragon discovers policies at start; kill -HUP is NOT sufficient to load a newly-dropped policy):
sudo mkdir -p /etc/tetragon/policies
sudo cp devops/agents-targets/tetragon/tracing-policy-stellaops-default.yaml \
/etc/tetragon/policies/stellaops-default.yaml
docker run -d --name tetragon \
--privileged --pid=host --cgroupns=host \
-p 127.0.0.1:54321:54321 \
-v /sys/kernel/btf/vmlinux:/var/lib/tetragon/btf:ro \
-v /sys/fs/bpf:/sys/fs/bpf \
-v /etc/tetragon/policies:/etc/tetragon/policies:ro \
<registry>/stellaops/tetragon:<version> \
--tracing-policy-dir /etc/tetragon/policies \
--server-address 0.0.0.0:54321 \
--disable-kprobe-multi
Notes:
--disable-kprobe-multiis REQUIRED on kernels wherebpf_multi_kprobefails to load (some 6.x Ubuntu generic kernels reject the consolidated multi-kprobe BPF object withprogram generic_kprobe_event: load program: invalid argument). Disabling forces per-kprobe attach; functionality is unchanged, throughput is slightly lower.- Tetragon’s server on
:54321is gRPC, NOT REST.curl http://.../v1/healthreturns garbage. Use the bundledtetraCLI (or the gRPC health server on:6789).
To reload a policy after editing it on disk, restart the container:
docker restart tetragon
Health and policy probe (gRPC via the bundled CLI):
docker exec tetragon tetra --server-address 127.0.0.1:54321 status
# → Health Status: running
docker exec tetragon tetra --server-address 127.0.0.1:54321 tracingpolicy list
# → ID NAME STATE ... SENSORS MODE
# 1 stellaops-default enabled ... generic_kprobe enforce
Live event stream (for verification only; do not leave this running in production):
docker exec tetragon tetra --server-address 127.0.0.1:54321 getevents -o json
Minimum kernel: ≥ 5.4 with BTF (verify ls /sys/kernel/btf/vmlinux). Ubuntu 22.04 / 24.04 kernels ship BTF by default; older RHEL 8 stock kernels do not.
Diagnostics
Each collection cycle publishes a local LinuxHostAgentSnapshot with:
- Agent.Core agent and host identity.
- Capability flags.
- Health state and reason code.
- Publishable digest-backed runtime statuses.
- Blocked statuses with deterministic reason codes.
- Optional runtime observations and observation source issues.
- Evidence publication diagnostics for publish attempts, published, retried, quarantined, dropped, duplicate observations, and the last reason code.
- Local spool count.
Important reason codes:
runtime_status.no_publishable_digestruntime_status.source_failedruntime_status.digest_missing_or_malformedruntime_status.staleruntime_status.ambiguouslinux.evidence.publish_exceptionlinux.spool.fulllinux.spool.retry_exhaustedagent_core.noop_publisher.not_configuredruntime_facts.callgraph_id_not_configured(retried)runtime_facts.no_symbolicated_frames(dropped)
Tetragon bridge health reason codes include tetragon_bridge.disabled, tetragon_bridge.starting, tetragon_bridge.connected, tetragon_bridge.disconnected, tetragon_bridge.event_malformed, tetragon_bridge.event_too_large, tetragon_bridge.event_unsupported, and tetragon_bridge.container_missing.
Tetragon export endpoint unreachable
Triggered when the Tetragon bridge health reports tetragon_bridge.disconnected. First confirm the transport gap described under Optional Tetragon: the export client needs an HTTP/NDJSON endpoint, not the Tetragon gRPC port. Then walk this list in order:
Is the container up?
docker ps --filter name=tetragon --format '{{.Names}} {{.Status}}' docker logs --tail 50 tetragon 2>&1If the container has
Exited (1), scroll to the LASTlevel=fatalline in the logs; common failures are below.Health probe via the bundled CLI (REST will not work — the server is gRPC):
docker exec tetragon tetra --server-address 127.0.0.1:54321 statusExpected:
Health Status: running. If this hangs > 5 s the gRPC server has not bound; checkdocker logs tetragonfor “Starting gRPC server”.Port collision. Tetragon listens on
:54321(configurable via--server-address). Verify nothing else is bound:sudo ss -tlnp | grep 54321If a stale process holds the port, restart the host or stop the offender.
Kernel BTF missing. The container needs
/sys/kernel/btf/vmlinuxmounted in. Inside the container:docker exec tetragon ls /var/lib/tetragon/btfExpected: a non-zero file. If empty, your kernel does not ship BTF (typical for stock RHEL 8). Install a BTF-bearing kernel or extract one via
pahole.bpf_multi_kprobe_v61.ofails to load. Symptom indocker logs:level=fatal msg="Failed to start tetragon" error="sensor generic_kprobe ... failed prog /var/lib/tetragon/bpf_multi_kprobe_v61.o ... load program: invalid argument"The kernel rejects Tetragon’s consolidated multi-kprobe program. Re-launch the container with
--disable-kprobe-multi(see install section above). Observed on Ubuntu 6.8.0-111 generic.AppArmor or SELinux denying eBPF. Symptom:
permission deniedonBPF_PROG_LOADorBPF_MAP_CREATEindmesg. The Stella host agent does not carry an AppArmor profile. If your host hardening adds one, it must allowbpfsyscalls and/sys/fs/bpfread+write on the Tetragon container. Quick test:sudo aa-statusandsudo cat /var/log/audit/audit.log | grep DENIED | tail -20. SELinux:getenforceandausearch -m AVC -ts recent.Tracing policy syntax error. Symptom:
level=warning msg="Failed to load TracingPolicy"indocker logs. Validate the YAML:python3 -c "import yaml; print(yaml.safe_load(open('/etc/tetragon/policies/stellaops-default.yaml')))"If you edit the policy in place, restart the container — Tetragon does not hot-reload policies on
SIGHUP.Observations arrive but Signals is not getting facts. That is not a Tetragon problem. Check
RuntimeInstrumentation:RuntimeFacts:BaseAddress(empty keeps the no-op publisher), a callgraph id (RuntimeInstrumentation:RuntimeFacts:CallgraphIdorCallgraphIdByImageDigest), and the snapshot’s publication last-reason code (runtime_facts.*). Facts are produced only from symbolicated frames. Aruntime_facts.rejectedreason with status 401 or 403 means the agent’s Authority client is wrong or lackssignals:write. Aruntime_facts.callgraph_not_foundreason means Signals does not hold the configured callgraph id: upload that callgraph or correct the mapping. The agent quarantines such entries instead of retrying them.
If steps 1-8 all pass but the bridge still reports tetragon_bridge.disconnected, capture docker logs tetragon plus the agent log line for escalation.
