Developer tools

Developer tools

The agent

iohr-agent, the data plane in your own network. Install it as an extension, with Helm or as a package, enroll it, declare its checks, and verify the release.

iohr-agent runs checks for the platform inside your own network, where the systems are. It dials out to the platform, does only what a local policy file allows, and sends back timings and verdicts, never content. Design: RFC 0029. Source: inorbithr/dataplane, Apache-2.0.

The current release is 0.1.0-alpha.7, a pre-release. The enrollment routes, the session and the scopes are in the public guide: Installing an agent.

  • Dials out only. One WebSocket to wss://api.inorbit.hr/v1/agents/session. Nothing listens for the network, so there is no inbound firewall rule to ask for.
  • Its own identity. Enrolling exchanges a one-time token for an OAuth2 client bound to a key the agent makes on its machine. The private key never leaves it; access tokens last 15 minutes and live in memory.
  • A local policy that wins. policy.toml names the networks and hosts it may reach, the work it accepts and the ceilings on it. Anything outside is refused, and the refusal with its reason shows in the console.
  • Credentials stay where they are. A check names a reference (vault:kv/staging/app#token, k8s:ns/name#key, env:NAME, file:/path) that the agent reads at the moment of the call.
  • Checks today: http (status, latency, certificate expiry), tcp, tls and grpc_health. Load and faults come later and are refused until then.

Install

For a laptop or a pipeline. Install iohr first.

iohr login
iohr ext install agent          # verifies the signature and provenance, pins the digest
iohr agent init --environment staging --domain example.com --account acme
iohr agent run

iohr agent init writes agent.toml and policy.toml, checks them with the platform, makes the enrollment with your sign-in and enrolls. Without --yes it asks for what is missing. --allow adds a network or host the agent may reach (repeatable), --secret-store names where check credentials live (none, env, file, k8s, vault), and --no-enroll makes the enrollment but leaves the token in the state directory for enroll --token-file.

The image is ghcr.io/inorbithr/iohr-agent. Every artifact is an OCI artifact, so a mirror copies them with their signatures (oras cp -r, cosign copy); point Helm or iohr config set ext.registry at it.

Enroll

An agent is enrolled for one environment and for domains your account has verified. In the console, open Agents and choose Enroll an agent, or call the API with a key that holds agents:write:

curl -X POST https://api.inorbit.hr/v1/accounts/orgs/$ORG_ID/agents/enrollments \
  -H "Authorization: Bearer $INORBIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environment":"staging","domains":["example.com"],"name":"edge-1"}'

The answer's token (ioe_…) enrolls one agent, once, within the hour, and is shown only in that answer. Keep it out of the command line, where shell history and the process list keep it:

iohr agent enroll --token-file ./inorbit-agent-staging.token   # or iohr-agent enroll …

The agent deletes the file once it has enrolled. iohr agent init does all of this with your sign-in instead. To take an agent away, Revoke it in the console: its session closes within seconds and it cannot get another token.

The policy

policy.toml belongs to whoever runs the agent. Unknown keys are errors, so a typo never silently allows something:

environment = "staging"

[domains]
bound = ["example.com"]

[networks]
allow = ["db.internal", "*.svc.cluster.local"]   # CIDRs, addresses, host names, *.suffix

[work]
checks = true
load = false
faults = false
surfaces = ["http", "tcp", "tls", "grpc_health"]

[ceilings]
max_concurrent_jobs = 4
max_job_ms = 30000
max_jobs_per_minute = 120

[secrets]
allow = ["vault:kv/staging/*", "k8s:checks/*"]

A host name is refused before any DNS query unless it is named in allow or lies inside a bound domain; it is then resolved once and the connection goes to the checked address. deny (link-local, cloud metadata and unspecified addresses by default) is never connected to. Redirects are not followed. iohr agent policy check --target URL tests a target against the file. The full reference: policy.md.

Declared checks: checks.toml

An optional checks.toml next to agent.toml lists what this agent watches. On every connection the agent sends the list; the platform turns each entry into a monitor managed by this agent, with the same scheduler, history and alerts as any other monitor. The file is the one source of truth: managed monitors are read-only in the console and the API, and changing the file and restarting the agent changes them. Design: RFC 0040.1.

[[check]]
name = "api"
surface = "http"
target = "https://api.example.com/healthz"
every = "60s"
expect = { status = 200, max_ms = 2000 }
fail_after = 2
rfc = "0029"

[[check]]
name = "api-tls"
surface = "tls"
target = "api.example.com"
every = "1h"
expect = { valid_for_days = 14 }

# Tests that must fail: a guard that stops guarding turns its monitor down.
[[refuse]]
name = "private-is-refused"
target = "https://db.internal/"
by = "policy"
every = "5m"

[[refuse]]
name = "api-needs-a-token"
surface = "http"
target = "https://api.example.com/v1/me"
expect = { status = 401 }
every = "5m"
KeyMeaning
name1 to 64 of a-z 0-9 -, unique; the monitor's key, so renaming an entry replaces its monitor
surfacehttp, tcp, tls or grpc_health; default http for a URL, tcp for host and port
targeta URL, "host:port", a bare host for tls, or a table; never a user or password in a URL
everyfrom one minute to 24 hours: "60s", "5m", "1h", "1d"
expectstatus, max_ms (up to 30000), valid_for_days (up to 365); without status, below 400 passes
fail_afterfailed runs in a row before the monitor goes down, 1 to 5 (default 2, or 1 for [[refuse]])
autha secret reference sent as the authorization header; the policy's [secrets] allow must permit it
rfcthe platform RFC this check proves; the RFC's page then shows it as proved
byfor [[refuse]]: "policy" (this agent refuses) or "platform" (the target is outside verified domains)

At most 50 entries. Lint the file against the policy before the platform sees it, offline; exit 1 on any error:

iohr agent checks lint                  # the files agent.toml names
iohr agent checks lint --resolve        # also resolve names, as a job would

What leaves the machine: the declared targets as written (full URLs with path and query, host names, ports), secret references and RFC numbers. Never a secret value. The platform answers what it accepted and why it rejected the rest; the agent's page in the console shows both. The full reference: checks.md.

Operating it

CommandWhat it does
iohr agent initWrite and check agent.toml and policy.toml, make the enrollment and enroll
iohr agent enroll --token-file FILEMake the key and enroll
iohr agent runConnect and work until stopped (exit 3: revoked)
iohr agent statusRunning, connected, policy hash, what was sent
iohr agent policy check [--target URL]Validate the policy; test a target against it
iohr agent checks lint [--resolve]Validate checks.toml against the policy, offline

Without iohr the same commands are iohr-agent <command>. A read-only status page answers on loopback only, at port 7790 of the agent's machine (JSON at /status.json), and counts every frame sent. Telemetry goes to your own OpenTelemetry collector when [telemetry] enabled = true in agent.toml; nothing is exported by default.

Verify a release

Every release is built and signed by https://github.com/inorbithr/dataplane/.github/workflows/release.yml at a tag v<version>, with an identity from GitHub Actions. No long-lived signing key exists.

ArtifactSignatureProvenanceSBOM
Imagecosign keylessSLSA v1, OCI referrerCycloneDX, OCI referrer
Helm chartcosign keylessSLSA v1, OCI referrer
iohr extensionSigstore bundleSLSA v1 bundle
Archives, .deb, .rpmSLSA v1 (gh attestation)iohr-agent.cdx.json

The image and the chart:

IDENTITY='^https://github.com/inorbithr/dataplane/.github/workflows/release.yml@refs/tags/v'
cosign verify ghcr.io/inorbithr/iohr-agent@sha256:<digest> \
  --certificate-identity-regexp "$IDENTITY" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/inorbithr/iohr-agent@sha256:<digest> \
  --repo inorbithr/dataplane --signer-workflow inorbithr/dataplane/.github/workflows/release.yml

The same cosign verify arguments work in a Kyverno or Sigstore policy-controller admission policy, so a cluster runs only images this workflow signed. The packages:

gh attestation verify iohr-agent_0.1.0.alpha.7-1_amd64.deb --repo inorbithr/dataplane
sha256sum -c SHA256SUMS

iohr ext install agent checks the extension's signature and provenance against the same identity before anything is written (Extensions). Each release also carries iohr-agent.openvex.json, which says which known vulnerabilities affect it. The releases are SLSA Build L2: the signing step runs apart from the build, on GitHub-hosted runners. The full page: verifying a release.