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.tomlnames 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,tlsandgrpc_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 runiohr 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"| Key | Meaning |
|---|---|
name | 1 to 64 of a-z 0-9 -, unique; the monitor's key, so renaming an entry replaces its monitor |
surface | http, tcp, tls or grpc_health; default http for a URL, tcp for host and port |
target | a URL, "host:port", a bare host for tls, or a table; never a user or password in a URL |
every | from one minute to 24 hours: "60s", "5m", "1h", "1d" |
expect | status, max_ms (up to 30000), valid_for_days (up to 365); without status, below 400 passes |
fail_after | failed runs in a row before the monitor goes down, 1 to 5 (default 2, or 1 for [[refuse]]) |
auth | a secret reference sent as the authorization header; the policy's [secrets] allow must permit it |
rfc | the platform RFC this check proves; the RFC's page then shows it as proved |
by | for [[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 wouldWhat 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
| Command | What it does |
|---|---|
iohr agent init | Write and check agent.toml and policy.toml, make the enrollment and enroll |
iohr agent enroll --token-file FILE | Make the key and enroll |
iohr agent run | Connect and work until stopped (exit 3: revoked) |
iohr agent status | Running, 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.
| Artifact | Signature | Provenance | SBOM |
|---|---|---|---|
| Image | cosign keyless | SLSA v1, OCI referrer | CycloneDX, OCI referrer |
| Helm chart | cosign keyless | SLSA v1, OCI referrer | |
| iohr extension | Sigstore bundle | SLSA v1 bundle | |
Archives, .deb, .rpm | SLSA 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.ymlThe 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 SHA256SUMSiohr 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.
Extensions
Programs that iohr installs from an OCI registry, verifies, pins and runs, without handing them your long-lived credentials.
Connections
Connect an account's incident, chat, code, enterprise and AI tools once, with a key or by signing in, and let products and keys act on them through a person's grant.