THE PROJECT IS WIP.
How to spin up the project:
- clone the repo and checkout the branch
feat-portability
- in the source folder
docker compose up
- mount the hostnames in your
/etc/hosts
file to use the traefik redirection:127.0.0.1 frontend 127.0.0.1 keycloak 127.0.0.1 backend 127.0.0.1 docs
- Access the components at
http://component_name
ex:http://frontend
- Login as users:
IIT username: iit-gennaro pswd: gennaro HOS username: dottore-alice pswd: alice IMT username: imt-vincenzo pswd: vincenzo UNIMI: ricercatore-stefano pswd: stefano
This project is a Pilot study for developing a platform to support the Serenade medical study by Policlinico di Milano in collaboration with Universita' degli studi di Milano. The platform is needed to facilitate the intercollaboration between the Hospital personnel, the hardware installators and the University team of researchers overseeing the study.
Considering the GDPR's classification of medical data as Special Category Data, the platform ensures precise management and monitoring of data accesses. To achieve correct data access governance the platform follows RBAC (Role-Based Access Control) authorization using the OpenID Connect standard; the access are monitored and store using a modern observability stack.
The actors interacting with the system are: hospital personnell (HOS) inputting data about patients, IIM and IIT which are installation teams for the needed hardware in the patients home and the are the researchers overseeing the study (UniMi).
OpenTelemetry | Jaeger | Loki | Grafana |
The objective is to observe and store accesses by users to portions of the data. Logs, metrics and traces are employed with this aim.
Grafana can give general observability over the health of the platform using the Prometheus metrics exposed by the backend and logs stored on Loki.
Thanks to OpenTelemetry's context injection each log can be linked to the request that originated it, which can be then observed on the Jaeger tracing dashboard. Traces allow to observe each request's path as it passes through the microservices.
Frontend NextJS tracing is achieved by using @vercel/otel an Opentelemetry automatic tracing instrumentation.
Backend tracing is achieved using opentelemetry-instrumentation-fastapi
and opentelemetry-instrumentation-sqlalchemy
, automatic OpenTelemetry tracing instrumentation libraries.
Backend Prometheus metrics exposure is achieved using prometheus-fastapi-instrumentator
which creates a /metrics
endpoint.
Backend logging is achieved using opentelemetry-instrumentation-logging
interfacing with the Python native logger to export logs in OpenTelemetry protocol (OTLP).
The OpenTelemetry Collector acts as a single interface to send observability data to and redistributes them to the relative backends.
Python | FastAPI | Pydantic | SQLAlchemy |
The backend features a REST api structure and serves requests under RBAC enforced by using Keycloak jwt tokens. The API is written using the FastAPI framework, SQLAlchemy is used for ORM interaction with the database and Pydantic is used for data schema validation.
The RBAC auth and authz is implemented achieved by leveraging the KeycloakOpenID
and FastAPI integration; everytime a request is sent to the backend before serving a response the jwt token is checked for validity and authorization based on user role against Keycloak.
Using the OpenAPI specification FastAPI creates automatic API endpoint documentation with a Swagger UI at the /docs
endpoint.
All the endpoints have their request and response body parameters documented.
Inside the backend/app
folder:
.
βββ app
βββ api ## rest endpoints
β βββ api.py ## url routes
β βββ deps.py ## dependency injections
β βββ endpoints
β βββ auth.py
β βββ installations.py
β βββ patients.py
| ...
βββ crud ## functions accessing the db
β βββ crud_installation.py
β βββ crud_patient.py
| ...
βββ models ## SQLAlchemy db object models
β βββ notes.py
β βββ patient.py
| ...
βββ schemas ## Pydantic logical object schemas
β βββ installation.py
β βββ patient.py
| ...
βββ core ## various configs
β βββ config.py
β βββ keycloak_config.py
β βββ security.py
βββ db ## db configs
β βββ base_class.py
β βββ session.py
βββ main.py
βββ utils ## various utils
βββ local_utils.py
The database table storing data that could identify patients is stored under transparent data encryption as additional security measure by using the pg_tde
functionality offered by this Percona postgreSQL16 distribution.
The frontend is in NextJS, NextAuth.js offers interoperability with Keycloak. NextJS provides serverside loading, where that is possible requests tokens are checked for validity and authorization based on user role against Keycloak.
Inside the frontend/src
folder:
.
βββ src
βββ app
β βββ api
β β βββ auth ## Configs for NextAuth
β β β βββ ...
β β βββ documents ## NextJS rest api endpoints that forward requests to the backend
β β β βββ ...
β β βββ installations ## NextJS rest api endpoints that forward requests to the backend
β β β βββ ...
β β ...
β βββ installations ## NextJS routes
β β βββ ...
β βββ patients ## NextJS routes
β β βββ ...
| ...
βββ components ## react components
β βββ ...
βββ utils ## utils functions
| βββ ...
...
Keycloak is an open-source identity and access management solution that provieds IAM capabilities but can also serve as an adapter for external IdPs. It's used to create the RBAC Auth and Authz through the OpenID connect standard.
Patient(PID, I#)
Installation(I#)
PatientGeneral(I#, Surname, Address, PhoneNo)
PatientDetail(PID, Name, SSN, *DoB, *PoB, *Age)
SensitiveData(PID, ...)
Ticket(DateTime, I#, RefNo, Status, Notes)
Patient(PID)
Installation(I#)
Stream(I#, PID)
PatientGeneral(I#, Surname, Address, PhoneNo)
PatientDetail(PID, Name, SSN, *DoB, *PoB, *Age)
SensitiveData(PID, ...)
Ticket(DateTime, I#, RefNo, Status, Notes)
Patient(PID, DataNick)
Installation(I#, HouseNick)
Stream(SID, HouseNick, DataNick)
PatientGeneral(I#, Surname, Address, PhoneNo)
PatientDetail(PID, Name, SSN, *DoB, *PoB, *Age)
SensitiveData(SID, ...)
Ticket(DateTime, HouseNick, RefNo, Status, Notes)
Advantages of V2 model:
It's not neede to specify the capabilities for all lookup combinations, anyone who already has Key
to decrypt lookup tables is authorized a priori to perform any lookup combination on the same table.
Doctor | Monitor | Tech | |
---|---|---|---|
Patient | β | β | β |
Installation | β | β | β |
Stream | β | β | β |
Doctor | Monitor | Tech | |
---|---|---|---|
PatientGeneral | β | β | β |
PatientDetail | β | β | β |
SensitiveData | β | β | β |
Ticket | β | β | β |
sequenceDiagram
actor Doctor
participant Frontend
participant Backend
participant KeyCloak
Doctor ->> KeyCloak: login(email,pswd)
KeyCloak ->> Doctor: JWT
Doctor->>Frontend: ask_data(JWT, user_id)
Frontend->>Backend: get_data(JWT, user_id)
Backend->>KeyCloak: check_jwt(JWT)
KeyCloak->>Backend: jwt_ok
Backend->>Frontend: data
Frontend->>Doctor: html
sequenceDiagram
actor Doctor
participant App
participant API
participant KeyCloack
actor Admin
Doctor->>KeyCloack: Send (Email,Password)
KeyCloack->>Doctor: OK
KeyCloack-->>KeyCloack: Store
Admin->>KeyCloack: Add capabilities
sequenceDiagram
participant IdP
actor Doctor
participant App
participant KeyCloack
Doctor->>KeyCloack: Send (IdP)
KeyCloack->>Doctor: Ask (Token)
Doctor->>IdP: Check (Email,Password)
IdP->>Doctor: OK (Token)
Doctor->>KeyCloack: Send (Token)
KeyCloack->>IdP: Check (Token)
Some DB Framework (like PostgreSQL) supports fine-grained access control at the table level through the use of roles and permissions. You can assign specific privileges to those roles for particular tables. This allows you to control who perform certain actions on specific tables and log them.
-
Roles for different responsibilities in the application.
CREATE ROLE dottore; CREATE ROLE tecnico; CREATE ROLE monitor;
-
Grant Privileges to each role for the relevant tables.
GRANT SELECT, INSERT, UPDATE, DELETE ON PatientDetail TO dottore; GRANT SELECT, INSERT ON Ticket TO tecnico; GRANT SELECT ON SensitiveData TO monitor;
-
Assign Roles to users:
GRANT dottore TO dottore-alice; GRANT tecnico TO tecnico-bob; GRANT monitor TO monitor-charlie;
Then the dataset can be encypted using TDE (Transparent Data Encryption) on server side.
The KeyStore API must be a trusted microservice. It allows to:
- Don't save
Key
on client app - Regenerate
Key
when needed (forgotSecret
, reset client app)
sequenceDiagram
actor Doctor
participant App
participant KeyStore API
participant KeyCloack
Doctor->>KeyCloack: Send (Email,Password)
KeyCloack->>App: OK (Token)
App->>Doctor: Ask (Secret)
Doctor->>App: Set (Secret)
App->>KeyStore API: Generate Key (Secret, Token)
KeyStore API->>KeyCloack: Check Capabilities (Token)
KeyCloack->>KeyStore API: OK
KeyStore API->>App: Send (Key)
App->>App: Encrypt (Key, Secret)
Key
will be used to encrypt data on client side. Key is encryped with Secret
sequenceDiagram
actor Doctor
participant App
participant API DB
participant DataBase
Doctor->>App: Send (Secret)
App-->>App: Decrypt (Key, Secret)
alt PUSH
Doctor->>App: Get / Post (Patient)
App-->>App: Encrypt (Patient, Key)
App->>DataBase: Send (Encrypted(Patient))
end
alt PULL
DataBase->>App: Send (Encrypted(Patient))
App-->>App: Decrypt (Encrypted(Patient), Key)
App->>Doctor: Send (Patient)
end