Skip to content

IronShep POC — technology inventory

Every version below was read from the resolved dependency graph (cargo tree), the running toolchain, and the live database on 2026-08-23 — not from the version ranges in Cargo.toml. Those ranges are semver-flexible, so a fresh cargo build on a different day may resolve a newer patch. Where the two differ, the manifest range is shown alongside.

Repos: ironshep-core v0.2.0 · ironshep-edge v0.2.0 — both Rust edition 2021.

Shipping artefact is two static binaries. Release builds (LTO + symbol stripping) on aarch64-apple-darwin:

Binary Release size Contains
ironshep-core 7.5 MB gRPC ingest server, TimescaleDB writer, the web console, and the notification senders (SMTP + webhooks)
ironshep-edge 7.0 MB six protocol listeners and the buffering forwarder

No runtime, no interpreter, no node_modules, no container image required.


1. Language & toolchain

Component Version Notes
Rust (rustc) 1.98.0 (88d9e12ae) edition 2021
Cargo 1.98.0 (797e8a9bc)
Host triple (dev) aarch64-apple-darwin production target is x86_64-unknown-linux-gnu (RHEL 9)
Build deps (RHEL) gcc, openssl-devel openssl-devel is only needed by the opcua crate on the edge

No protoc anywhere. The gRPC contract is compiled by protox, a pure-Rust protobuf compiler, so there is no external binary to install or version-match on any host.

No Node, npm, or bundler. The web console is hand-written HTML/CSS/JS compiled into the core binary — see §5.


2. Shared foundation (both binaries)

Crate Resolved Manifest Role
tokio 1.53.1 1 (features full) async runtime
tonic 0.12.3 0.12 (feature tls) gRPC — server in core, client in edge
prost 0.13.5 0.13 protobuf codegen runtime
serde 1.0.229 1 (derive) serialization
serde_json 1.0.151 1 JSON — API responses, attributes columns
toml 0.8.23 0.8 config files
chrono 0.4.45 0.4 (serde) timestamps
tracing 0.1.44 0.1 structured logging
tracing-subscriber 0.3.23 0.3 (env-filter) RUST_LOG handling
anyhow 1.0.104 1 error handling

Build-dependencies (both): tonic-build 0.12.3, protox 0.7.2.

Transport security stack (transitive)

Crate Resolved Where
rustls 0.23.43 gRPC/mTLS + Postgres TLS
rustls 0.22.4 pulled in by an older-API dependency; both coexist
tokio-rustls 0.26.4 / 0.25.0 matching the two rustls majors
rustls-pemfile 2.2.0 certificate/key parsing
ring 0.17.14 cryptographic primitives
hyper 1.11.0 HTTP/2 transport under tonic and axum

TLS is rustls, not OpenSSL — no system libssl dependency at runtime, which keeps the RHEL deployment insensitive to the host's OpenSSL version.


3. ironshep-core — ingest server + console

Crate Resolved Manifest Role
sqlx 0.8.6 0.8 Postgres driver — features postgres, runtime-tokio, tls-rustls, chrono, json; default-features = false
axum 0.7.9 0.7 web console HTTP server
argon2 0.5.3 0.5 argon2id password hashing
rand 0.8.7 0.8 session token generation (OsRng)
sha2 0.10.9 0.10 hashes session tokens at rest
rpassword 7.5.4 7 no-echo password prompt for --add-user
reqwest 0.12.28 0.12 Slack/Teams webhook delivery — rustls-tls, json; default-features = false
lettre 0.11.23 0.11 email (SMTP) delivery — smtp-transport, tokio1-rustls-tls, builder; default-features = false

Both notification clients use rustls, so core still has no system OpenSSL dependency (matches the gRPC/DB TLS stack).

Dev-dependency: tokio-stream 0.1.19 — only for the mTLS integration test's ephemeral-port listener.

Queries are runtime-checked, not compile-time macros, so building core does not require a reachable database or a DATABASE_URL.

Unique crates in the graph, including transitive: 214.


4. ironshep-edge — protocol collector

Crate Resolved Manifest Role
rumqttc 0.24.0 0.24 MQTT subscriber
tokio-modbus 0.14.0 0.14 Modbus/TCP client — default-features = false, feature tcp
opcua 0.12.0 0.12 OPC-UA client — optional, feature-gated

Feature flags: default = ["opcua-support"]. Build without OPC-UA via cargo build --release --locked --no-default-features — that one crate carries the largest dependency subtree and the least stable API.

Hand-written, zero-dependency decoders (deliberate — these are the layers you want to understand when a field device sends something strange):

Protocol Module Coverage
SNMP / BER src/listeners/snmp_ber.rs v1 + v2c traps and informs, plus v2c Get/GetNext encoding and typed response parsing for the poller; unit-tested
BACnet/IP src/listeners/bacnet.rs BVLC/NPDU/APDU framing, full I-Am decode; unit-tested
Syslog src/listeners/syslog.rs RFC 3164 + RFC 5424, UDP and TCP

Unique crates in the graph, including transitive: 176.

Protocols carried today

Protocol Direction Default port
Syslog inbound UDP + TCP 5514
SNMP traps (v1/v2c) inbound UDP 5162
SNMP polling (v2c) outbound poll 161
BACnet/IP inbound UDP 47808
MQTT outbound to broker 1883
Modbus/TCP outbound poll 502 (1502 for the simulator)
OPC-UA outbound poll 4840
gRPC → core outbound, mTLS 50051

SNMP is entirely the hand-written BER codec — the poller added the encode direction to the same zero-dependency module. New listener: src/listeners/snmp_poll.rs (scalar Gets, GetNext table walks, outage transition events). SNMPv3 (USM) remains out of scope for both directions.

The other protocols on the marketing site (DNP3, EtherNet/IP, PROFINET, IEC 61850, S7, HART, CoAP, LoRaWAN, NetFlow) are roadmap, not implemented — see ironshep-site/CLAUDE.md.


5. Web console (inside ironshep-core)

Layer Technology Version / size
Server axum 0.7.9
Markup hand-written HTML5 app.html 3.6 KB · login.html 5.5 KB
Styles hand-written CSS, custom properties style.css 25.9 KB
Behaviour vanilla ES2020+, hash routing app.js 44.8 KB
Charts inline SVG, no library part of app.js
Login background Canvas 2D particle network part of login.html
Icons inline SVG + brand-kit SVG/ICO ~2.6 KB total

Layout is a fixed left icon rail (Fleet · Signals · Events · Settings) beside a content column; entity detail opens in a right slide-in drawer, and the sign-in screen carries the ironshep.com particle-network canvas. Collapses to a bottom bar on phones. Settings holds an admin-only notification panel (email/Slack/Teams channels + triggers), gated on the session role.

Zero JavaScript dependencies. Zero external requests. No React, no charting library, no web fonts, no CDN. Every asset is include_str!/ include_bytes!'d into the binary at compile time, which is what lets the console work on an air-gapped network — a hard requirement for the OT buyers this targets. Total UI payload: ~83 KB.

Typography uses the brand stack (Bookman Old Style → Cambria → Georgia serif; Calibri body; Courier New mono) resolved from system fonts, so nothing is fetched at runtime.

Browser APIs relied on: fetch, URLSearchParams, Canvas 2D (login background), localStorage (motion preference), pointer events, CSS custom properties, backdrop-filter. Targets current evergreen browsers; no transpilation or polyfills.


6. Data layer

Component Version Notes
PostgreSQL 16.14 production install via PGDG on RHEL 9
TimescaleDB 2.29.2 hypertables + compression policies on events, telemetry
timescaledb_toolkit 1.25.0 ships with the container; useful for rolling baselines in ironshep-ml
pgvector (vector) 0.8.6 vector(768) column on log_templates, reserved for Phase-2 LLM work
plpgsql 1.0 stock

Local dev image: timescale/timescaledb-ha:pg16 (digest sha256:07122702…44d1a49), published on host port 5433.

Tables: events, telemetry (hypertables) · assets, anomalies, log_templates, users, ui_sessions (regular).


7. Cryptography & auth

Concern Choice Detail
Edge ↔ core transport mTLS, mandatory no plaintext mode exists
Certificates ECDSA P-256, SHA-256 825-day leaves, single POC CA
Cert generation openssl CLI scripts/gen_certs.sh
Console passwords argon2id argon2 0.5.3, PHC-string format
Console sessions 256-bit random token OsRng; only SHA-256 stored server-side
Cookie HttpOnly, SameSite=Lax 12h default TTL
Roles admin / user notification setup is admin-only, enforced by an axum extractor (403 before body parse)
Notification secrets plaintext at rest SMTP password + webhook URLs in the DB; never returned to the browser (redacted to a *_set flag) — encrypting at rest is on the roadmap
Notification transport rustls webhooks over HTTPS; SMTP via STARTTLS/implicit TLS

gen_certs.sh explicitly passes -sha256 and -pkeyopt ec_param_enc:named_curve because LibreSSL on macOS defaults to SHA-1 signatures and explicit EC parameters, both of which rustls rejects. RHEL's OpenSSL 3 does the right thing by default; the flags are harmless there and essential on a Mac.

Console TLS is not implemented — port 8080 is plain HTTP in the POC. Front it with a reverse proxy or keep it on a management network. This is item 2 on the roadmap.


8. Test & simulation tooling

Tool Version Purpose
cargo test built-in 12 edge unit tests, 9 core unit tests, 2 core mTLS integration tests
openssl LibreSSL 3.3.6 (macOS dev) throwaway certs inside the integration test
Python 3.9.6 simulators only, never in the runtime path
pymodbus 3.8.6 Modbus pump simulator with modelled degradation
paho-mqtt not installed here required only by mqtt_sim.py
Docker 29.2.1 local TimescaleDB only; nothing is containerised in production
Bash 3.2.57 (macOS) / 5.x (RHEL) scripts are written to the 3.2 floor for portability

Simulator Python imports are all stdlib except pymodbus and paho.mqtt: asyncio, json, math, random, socket, struct, sys, time.


9. Deployment & operations

Component Technology
Target OS RHEL 9 (three hosts: database, core, edge)
Process supervision systemd, hardened units (ProtectSystem=strict, NoNewPrivileges, CAP_NET_BIND_SERVICE on edge only)
Firewall firewalld, scoped rich rules per source IP
SELinux stays Enforcing — no custom policy required
Scheduled jobs cron (crond) — synthetic traffic + health check on the edge
Log rotation logrotate
Source hosting GitLab
Web presence GitLab Pages (ironshep-site, separate repo)

10. Deliberate non-choices

Worth recording, because each was considered and rejected:

Not used Why
protoc binary protox compiles protobuf in pure Rust — one less host dependency to version-match
OpenSSL at runtime rustls avoids coupling to the host's libssl
React / Vue / any JS framework ~83 KB of hand-written UI beats a build pipeline for this handful of views, and keeps the air-gap story true
Any charting library inline SVG is ~200 lines and adds no supply-chain surface
Web fonts system-font stack; nothing fetched at runtime
Docker in production plain systemd units on RHEL; containers are dev-only
sqlx compile-time macros would require a live database to build
Kafka / NATS / a message bus the edge's bounded buffer plus core's gRPC backpressure covers POC scale
An API between core and ironshep-ml the anomalies table is the contract — see ironshep-core/docs/ML_CONTRACT.md

11. Not yet built

Named in the architecture, no code today:

Module Purpose Status
ironshep-ml anomaly detection → anomalies table contract defined, console side complete and waiting
ironshep-llm log-template mining + embeddings → log_templates schema column reserved (vector(768))
ironshep-security Phase-4 security module brand mark assigned (Anomaly Waveform), nothing else

Reproducing this inventory

source "$HOME/.cargo/env"
cd ironshep-core && cargo tree --depth 1 --edges normal
cd ../ironshep-edge && cargo tree --depth 1 --edges normal
rustc --version && cargo --version
docker exec ironshep-db psql -U ironshep -d ironshep \
  -tAc "SELECT extname||' '||extversion FROM pg_extension ORDER BY 1;"

Cargo.lock is committed in both repos, so the versions in this document are the versions a build produces anywhere — the RHEL hosts resolve exactly what was tested here. Lockfile format version 4.

To reproduce a build exactly as tested, and fail loudly rather than silently upgrading if anything drifts:

cargo build --release --locked

Use --locked in CI and in the deployment runbooks. To take deliberate upgrades: cargo update (whole graph) or cargo update -p <crate> (one crate), then re-run the test suites and refresh the version tables above.