Skip to content
FerroHEALTHPublic

About

A pure-Rust openEHR federation gateway: one AQL query in, standard AQL to every node, one answer out, after the openEHR Federation Tier with AQL proposal.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

383 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FerroFED

CI CodeQL OpenSSF Scorecard OpenSSF Best Practices Quality Gate Status Coverage License: BUSL-1.1 GitHub release (latest SemVer) Image pulls

Federation Tier 0.9.0 gateway points Federation Tier 0.9.0 node points Federation Tier 0.9.0 operator points AQL golden cases

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.

Status

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.

What it implements

  • 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 answers 501 there unless you name one member to serve it, and 501 for 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 xcpd feature of ihe-iti is 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.

Quickstart

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.sh

The 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'"}
EOF

The 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.

Install

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.yml

Every 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 --wait

The 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.

Documentation

  • 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.

Licence

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.

About

A pure-Rust openEHR federation gateway: one AQL query in, standard AQL to every node, one answer out, after the openEHR Federation Tier with AQL proposal.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages