An openEHR federation gateway, in pure Rust: where else the record is.
A record held by another organisation is out of reach today. FerroFED is a
transparent ITS-REST intermediary in front of several openEHR CDRs: a client
sends it an ordinary AQL query, with no federation syntax, and gets back an
ordinary ITS-REST result set whose meta.federation names each node with its
status. The gateway resolves the patient outside the query, through an
identifier cross-reference service, so no request the gateway composes for a
node carries a directly identifying patient identifier. It sends each node
standard AQL scoped to that node's own ehr_id, and merges the answers with
each node's provenance. It holds no clinical data of its own.
FerroFED is one of the FerroHEALTH family. The
documentation is at https://ferrofed.eu/docs/, and the design of record is
docs/architecture.md.
FerroFED is at v0.0.9 on its 0.0.x line, where each milestone is a release. The gateway serves the federated query with the patient resolved outside AQL, shapes the merged rows as one CDR would, routes follow-up reads and writes to the node that owns them, sends definition requests to the node you name, and holds stored queries itself.
Every client authenticates with an RFC 9068 access token from an issuer you
trust, carrying a SMART on openEHR scope for the operation and a purpose of
use, or through a proxy in the explicit edge mode
(client authentication).
Toward the nodes, the gateway authenticates as itself, with OAuth 2.0 client
credentials or token exchange and a signed assertion, the token bound to the
gateway's key with DPoP or to its TLS client certificate where you ask for
it, or with a bearer token or a user and password you configure per
endpoint, and never sends the client's own token.
Every request to a node carries the verified client in an
openEHR-federation-client token the gateway signs with its own key, which
each node can verify against the key set the gateway publishes
(what a node is told about the caller).
Localization through XCPD, the PIX Manager or the Dutch NVI, a PDQm step
ahead of resolution, the identity feed over PMIR, the registry read from an
mCSD directory, the audit of every IHE transaction to an ATNA repository,
the Dutch consent pre-filter Mitz, the Nuts and FAPI 2.0 grants toward a
node, and traces exported through OpenTelemetry shipped in v0.0.8. v0.0.9
added the conformance statement, ferrofed conformance run and the operator
console.
v0.0.10, being built on main, is EHDS readiness and the production gaps.
Built so far: every access to patient data recorded with the verified
caller, the professional and, where the issuer states one, the assurance
level, as Annex II, point 3.2,
asks of the European logging component
(#623,
#742,
the access log);
TLS on the listener
(#632); overload
protection (#631);
restarts without dropped requests
(#625); several
replicas (#626,
#627); and inbound
metrics behind an authenticated admin listener
(#629,
#635). Planned: the
logging component's review tools and retention by origin and category
(#521), the European
interoperability component served by the gateway, which today is the
library crates/eehrxf
(#522), and the
conformity file (#525).
Each tagged release is a product its manufacturer, Cadasto B.V., places on
the market under the Cyber Resilience Act; the reporting procedure and the
support period of each release are planned
(#762,
#763,
regulatory status).
The
claims page
lists what each release shipped, what is planned, and the limitations to
know before you deploy it.
- The openEHR Federation Working Group's
Federation Tier with AQL
specification, release candidate v0.9.0 at commit
7162d0c. The conformance statement claims its Federation-Gateway profile, point by point; the conformance matrix and the obligations checklist say which points and statements a test holds. - openEHR ITS-REST 1.1.0 on both faces, and openEHR AQL 1.1.0, through the
published
openehr-*crates. The gateway never federates the DEMOGRAPHIC area: it answers501there unless you name one member to serve it, and501for any ITS-REST path it does not serve (queries and API areas). - IHE PIXm ITI-83 for identity resolution and, without XCPD or the NVI, for localization, and IHE mCSD ITI-90 and ITI-91 for addressing: the registry can be read from a care services directory and kept in step with it.
- IHE PDQm ITI-78 and ITI-119 ahead of resolution, for a patient named by an identifier the cross-reference does not know.
- IHE XCPD ITI-55 for localization: the
xcpdfeature ofihe-itiis an Initiating Gateway, and an undirected patient query asks only the members whose communities it discovers, failing closed when a gateway does not answer. - IHE PMIR ITI-94 and ITI-93 for the identity lifecycle: the gateway subscribes at a Patient Identity Registry, and an authenticated merge drops the resolution bindings it could have made stale.
- IHE ATNA ITI-20: every IHE transaction the gateway makes or receives is
audited to an Audit Record Repository, ITI-55 as a DICOM message over
syslog and the FHIR profiles as BALP
AuditEvents over the FHIR Feed. - The Dutch Generic Functions of Annex B: NVI localization, the Mitz consent pre-filter, the URA of each organisation from an LRZa-sourced directory, and the Nuts and FAPI 2.0 authentication tracks toward a node.
The gateway beside four member CDRs: four FerroEHR instances on one
PostgreSQL server, with a database per node. compose.yaml runs the
published image of the product version, and the seed script creates
synthetic patients over each node's ITS-REST API: one at all four nodes, one
at two, one at one and one at none.
scripts/quickstart/signing-key.sh
docker compose up --wait
scripts/quickstart/seed.shThe first script writes the gateway's development signing key, once, with
openssl; the gateway signs the caller's identity onto every request to a
node with it.
Then send one ordinary ITS-REST query to the gateway for the patient every
node knows. scripts/quickstart/token.sh mints the access token the
quickstart gateway accepts, from a development issuer whose key pair it
generates with openssl on its first run:
curl -s http://127.0.0.1:8080/v1/query/aql \
-H "Authorization: Bearer $(scripts/quickstart/token.sh)" \
-H 'Content-Type: application/json' -d @- <<'EOF'
{"q": "SELECT c/uid/value FROM EHR e CONTAINS COMPOSITION c WHERE e/ehr_status/subject/external_ref/id/value = 'ffd-test-0001' AND e/ehr_status/subject/external_ref/namespace = 'urn:oid:2.999.1.1'"}
EOFThe answer is one ITS-REST RESULT_SET with a composition from each node in
rows, and meta.federation reports all four endpoints active. Each node
received a query scoped to its own ehr_id, with no patient identifier in
it. The quickstart resolves its synthetic patients through a static
development cross-reference, and its credentials are development values.
The container page walks
through a patient missing at some nodes, a query directed at one node, the
ports and the measured memory use.
Every release on the
releases page
ships the ferrofed binary for x86_64 and aarch64 Linux, on glibc and on
musl, each tarball with its checksum, SLSA provenance and SBOMs. The image
ghcr.io/ferrohealth/ferrofed carries the musl binary for linux/amd64 and
linux/arm64. Verify what you download before you run it:
gh attestation verify ferrofed-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz \
--repo FerroHEALTH/FerroFED \
--signer-workflow FerroHEALTH/FerroFED/.github/workflows/release-build.yml
gh attestation verify oci://ghcr.io/ferrohealth/ferrofed:X.Y.Z \
--repo FerroHEALTH/FerroFED \
--signer-workflow FerroHEALTH/FerroFED/.github/workflows/release-image.ymlEvery release also carries compose.yaml, which runs the gateway alone, at
that release's image, in front of the CDRs you already run, with two example
files it mounts. Download the three, edit registry.toml (your members) and
ferrofed.toml (your federation id, your PIX Manager and the credentials each
endpoint gets), put each credential file and the gateway's signing key in
secrets/, and start it:
for f in compose.yaml ferrofed.toml registry.toml; do
curl -LO "https://github.com/FerroHEALTH/FerroFED/releases/latest/download/$f"
done
# edit ferrofed.toml and registry.toml; put credential files in secrets/
# a P-384 key signs ES384; ec_paramgen_curve:P-256 gives an ES256 key instead
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 \
-out secrets/signing-key.pem
docker compose up --waitThe release compose file reads every credential from a file in secrets/.
ferrofed.toml also accepts a secret inline, under every profile, but a
value written there sits in the file and in every copy of it, so name a
file with the _file key instead
(configuration).
The container page
walks through each step.
ferrofed serve --config ferrofed.toml runs the gateway, and
ferrofed config check --config ferrofed.toml reports whether it would start
on that file. The
configuration chapter
covers every key, the registry and the identity service. The
production guide walks a
deployment from nothing to a first federated query over two CDRs: the
registry, an identity provider, the PIX Manager, the audit repository, a
reverse proxy and the checks before the first query.
- Evaluate: the specification, what FerroFED claims, conformance, versions and the licence.
- Regulatory status: FerroFED's intended purpose and its classification as an EHR system under the European Health Data Space Regulation, (EU) 2025/327, with what is built and what is planned.
- Data protection and the threat model: the personal data the gateway processes and how long it keeps it, the GDPR roles, NIS2 and the medical device question, and every trust boundary with its mitigations and open risks.
- Operate: deployment, the container, configuration, admission, health and metrics.
- Integrate: what a client sends and gets back, and every error code.
- Contribute: the tracker and the checks, beside CONTRIBUTING.md.
FerroFED is source-available under the Business Source License 1.1. The parameters that apply, the Licensor, the Licensed Work, the Additional Use Grant and the Change Date, are in LICENSE: free for non-commercial production use, a commercial licence for any other production use, and Apache 2.0 four years after each version is published. A commercial licence is arranged with Cadasto B.V., the Licensor, which handles the business side of FerroFED: write to info@cadasto.com or use https://www.cadasto.com/contact/. Technical questions go to the maintainer named in MAINTAINERS.md.
The brand assets under assets/brand/ are part of the Licensed Work.
Contributions carry the terms in CONTRIBUTING.md: you keep your copyright, and you grant the Licensor the relicensing right that keeps the work one work under one licensor. There is no separate agreement to sign.