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

1 star

Watchers

0 watching

Forks

Latest commit

 

History

538 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FerroFED

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

Federation Tier 1.0.0 gateway points Federation Tier 1.0.0 node points Federation Tier 1.0.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. The family book holds what every product shares: how the products fit together, the manufacturer, and licensing and trademarks.

Status

FerroFED is at v0.0.11 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 brought EHDS readiness and the production gaps. Every access to patient data is recorded with the verified caller, the professional, the assurance level where the issuer states one, the categories of the data and how long the record is kept, as Annex II, point 3.2, asks of the European logging component (#623, the access log). With [fhir] set, a FHIR face answers the patient summary in the European exchange format and receives a patient summary document into the member you name for it (#809, #802). The book has an information sheet and instructions for use. For production, v0.0.10 adds identity resolution through a PIX Manager the members feed (#622), restarts without dropped requests (#625), several replicas (#626, #627), inbound metrics behind an authenticated admin listener (#629, #635), overload protection (#631), TLS on the listener (#632), and the production, upgrade, data-protection and threat-model pages. It implements the Federation Tier specification's 1.0.0 release. Each tagged release is a product its manufacturer, Cadasto B.V., places on the market under the Cyber Resilience Act. Only the newest release is supported: upgrading is free, and Cadasto B.V. keeps publishing security fixes, in new releases, for at least five years after each release (SECURITY.md, regulatory status). The manufacturer's side of the CRA, the same for every FerroHEALTH product, is in the family book (the Cyber Resilience Act). v0.0.11 continues EHDS readiness. The FHIR face searches and reads a patient's electronic prescriptions and receives a dispensation into the member that owns its prescription, passed through in the HL7 Europe MPD profiles from the members that serve them, which decide what can still be dispensed (prescriptions and dispensations). A received document written under a person's own grant, or a representative's, is marked as theirs, and the access records carry a security label per category and origin, name the patients a population query read, and log each semantic transformation. With config.ui = "managed", the configuration is kept as versioned revisions that an operator saves, applies, confirms and rolls back from the redesigned operator console, and every replica follows (#575). The book adds the Annex II checklist, the technical documentation, the cybersecurity risk assessment and the CRA information to the user. Planned: the national contact point's connector (#524), the logging component's log access interface and review tools (#521, #660), the rest of the European interoperability component, prescriptions from openEHR medication data among it (#522, #523, #1043), and the rest of the conformity file (#525). 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 1.0.0 (tag 1.0.0, commit 7dcf0f4). 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. The terms every FerroHEALTH product shares, how to arrange a commercial licence with Cadasto B.V., and the brand terms of the FerroHEALTH names and marks are in the family book (licensing and trademarks). 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

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages