Git Product home page Git Product logo

fhir-access-proxy's Introduction

FHIR Information Gateway

Build Status

FHIR Information Gateway is a simple access-control proxy that sits in front of a FHIR store (e.g., a HAPI FHIR server, GCP FHIR store, etc.) and controls access to FHIR resources.

Note: "gateway" and "proxy" are used interchangably here, as the gateway is implemented as a proxy server.

The authorization and access-control have three components; one of them is this access proxy. The other two are an Identity Provider (IDP) and an Authorization server (AuthZ). The responsibility of this pair is to authenticate the user and issue access tokens (in JWT format and using authorization flow of OAuth 2.0). The requests to the access proxy should have the access token as a Bearer Authorization header. Based on that, the proxy decides whether to grant access for a FHIR query.

Modules involved in FHIR authorization/access-control

For more information on the technical design, see the design doc.

Modules

The proxy consists of a core, which is in the server module, and a set of access-checker plugins, which can be implemented by third parties and added to the proxy server. Two sample plugins are implemented in the plugins module. There is also a sample exec module which shows how all pieces can be woven together into a single Spring Boot app. To build all modules, from the root run:

mvn package -Dspotless.apply.skip=true

The server and the plugins can be run together through this executable jar ( --server.port is just one of the many default Spring Boot flags):

java -jar exec/target/exec-0.1.0.jar --server.port=8081

Note that extra access-checker plugins can be added through the loader.path property (although it is probably easier to build them into your server):

java -Dloader.path="PATH-TO-ADDITIONAL-PLUGINGS/custom-plugins.jar" \
  -jar exec/target/exec-0.1.0.jar --server.port=8081

The plugin library can be swapped with any third party access-checker as described in the plugins directory and the wiki.

Note: Spring Boot is not a requirement for using FHIR Information Gateway; we just use it to simplify the MainApp. The only Spring-related requirement is to do a @ComponentScan to find all access-checker plugins in the classpath.

Configuration parameters

The configuration parameters are provided through environment variables:

  • PROXY_TO: The base url of the FHIR store e.g.:

    export PROXY_TO=https://example.com/fhir
  • TOKEN_ISSUER: The URL of the access token issuer, e.g.:

    export TOKEN_ISSUER=http://localhost:9080/auth/realms/test

    The above example is based on the default config of a test IDP+AuthZ Keycloak server. To see how this server is configured, check the docker/keycloak directory. If you want to use a SMART-on-FHIR app use this realm instead:

    export TOKEN_ISSUER=http://localhost:9080/auth/realms/test-smart
  • ACCESS_CHECKER: The access-checker to use. Each access-checker has a name (see plugins for details) and this variable should be set to the name of the plugin to use. For example, to use one of the sample plugins include one of:

    export ACCESS_CHECKER=list
    export ACCESS_CHECKER=patient

    For more information on how access-checkers work and building your own, see Understanding access checker plugins.

  • ALLOWED_QUERIES_FILE: A list of URL requests that should bypass the access checker and always be allowed. AllowedQueriesChecker compares the incoming request with a configured set of allowed-queries. The intended use of this checker is to override all other access-checkers for certain user-defined criteria. The user defines their criteria in a config file and if the URL query matches an entry in the config file, access is granted. AllowedQueriesConfig provides all the supported configurations. An example of this is hapi_page_url_allowed_queries.json. To use this file with ALLOWED_QUERIES_FILE:

    export ALLOWED_QUERIES_FILE="resources/hapi_page_url_allowed_queries.json"
  • BACKEND_TYPE: The type of backend, either HAPI or GCP. HAPI should be used for most FHIR servers, while GCP should be used for GCP FHIR stores.

Gateway to server access

The proxy must be able to send FHIR queries to the FHIR server. The FHIR server must be configured to accept connections from the proxy while rejecting most other requests.

If you use a GCP FHIR store you have the following options:

  • If you have access to the FHIR store, you can use your own credentials by doing application-default login. This is useful when testing the proxy on your local machine, and you have access to the FHIR server through your credentials.
  • Use a service account with required access (e.g., "Healthcare FHIR Resource Reader", "Healthcare Dataset Viewer", "Healthcare FHIR Store Viewer"). You can then run the proxy in the same GCP project on a VM with this service account.
  • [not-recommended] You can create and download a key file for the above service account, then use it with
    export GOOGLE_APPLICATION_CREDENTIALS="PATH_TO_THE_JSON_KEY_FILE"

Once you have set all the above, you can run the proxy server. The sample exec module uses Apache Tomcat through Spring Boot and the usual configuration parameters apply, e.g., to run on port 8081:

java -jar exec/target/exec-0.1.0.jar --server.port=8081

Docker

The proxy is also available as a docker image:

$ docker run -p 8081:8080 -e TOKEN_ISSUER=[token_issuer_url] \
  -e PROXY_TO=[fhir_server_url] -e ACCESS_CHECKER=list \
  us-docker.pkg.dev/fhir-proxy-build/stable/fhir-gateway:latest

Note if the TOKEN_ISSUER is on the localhost you may need to bypass proxy's token issuer check by setting RUN_MODE=DEV environment variable if you are accessing the proxy from an Android emulator, which runs on a separate network.

Try the proxy with test servers in Docker.

GCP note: if this is not on a VM with proper service account (e.g., on a local host), you need to pass GCP credentials to it, for example by mapping the .config/gcloud volume (i.e., add -v ~/.config/gcloud:/root/.config/gcloud to the above command).

How to use this proxy

Once the proxy is running, we first need to fetch an access token from the TOKEN_ISSUER; you need the test username and password plus the client_id:

$ curl -X POST -d 'client_id=CLIENT_ID' -d 'username=testuser' \
  -d 'password=testpass' -d 'grant_type=password' \
"http://localhost:9080/auth/realms/test/protocol/openid-connect/token"

We need the access_token of the returned JSON to be able to convince the proxy to authorize our FHIR requests (there is also a refresh_token in the above response). Assuming this is stored in the ACCESS_TOKEN environment variable, we can access the FHIR store:

$ curl -X GET -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
'http://localhost:8081/Patient/f16b5191-af47-4c5a-b9ca-71e0a4365824'
$ curl -X PUT -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
'http://localhost:8081/Patient/f16b5191-af47-4c5a-b9ca-71e0a4365824' \
-d @Patient_f16b5191-af47-4c5a-b9ca-71e0a4365824_modified.json

Of course, whether a query is accepted or denied, depends on the access-checker used and the ACCESS_TOKEN claims. For example, for ACCESS_CHECKER=list there should be a patient_list claim which is the ID of a List FHIR resource with all the patients that this user has access to. For ACCESS_CHECKER=patient, there should be a patient_id claim with a valid Patient resource ID.

Acknowledgement

This proxy is implemented as a HAPI FHIR Plain Server, starting from this hapi-fhirstarters-simple-server example.

fhir-access-proxy's People

Contributors

anchita-g avatar bashir2 avatar dependabot[bot] avatar dubdabasoduba avatar jjtswan avatar ndegwamartin avatar omarismail94 avatar rehammuzzamil avatar vivekmittal07 avatar williamito avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fhir-access-proxy's Issues

Support AllowedQueriesChecker for Bundle Entry Requests

As of now, the AllowedQueriesChecker only checks for by-passing any access based checks on a request by the request path. However, in case of a bundled request, we should allow the same checks for by-passing on each bundle entry request.

Abstract out bundle processing in AccessCheckers to a separate entity

In reference to the conversation here in PR , remove the duplicated logic of Bundle processing in PatientAccessChecker. Possibly refactor any Bundle processing related code in other AccessCheckers as well.
Idea is to create a separate abstraction for Bundle processing which can be used by different implementations like PatientFinder and for any specific implementation of Bundle processing in the AccessCheckers.

Support client supplied Accept header in API requests

Proxy is always setting the response content type to a default header - proxyResponse.getResponseWriter
We are ignoring the Accept header sent by the client.

We expect the FHIR response to be in JSON. If we send application/xml then this will break the code.
fhir-access-proxy/plugins/src/main/java/com/google/fhir/proxy/plugin/AccessGrantedAndUpdateList.java

I tried the create Patient API with "Accept: application/fhir+xml" and I get this error in AccessGrantedAndUpdateList "Content does not appear to be FHIR JSON, first non-whitespace character was: "

We need to add support for client specified Accept header and return the corresponding Content-type

Track test coverage

We should track and expose test-coverage data publicly. A long time ago, we used Codecov for the Analytics repo but we need to check the spectrum of possible options now again. This is also related to #98 and what we choose for continuously running our tests.

Return '403' Forbidden error message to an unauthorized user

Current
When user access is denied, an error code of 401 is thrown.
Unauthorized401

Expected
It should return 403 Forbidden error message to the unauthorized user.

It worked by:
Updating exception to ForbiddenOperationException.class instead of throwing AuthenticationException.class in
https://github.com/google/fhir-access-proxy/blob/3bf8a400e257f234f677154442f63570cd131307/server/src/main/java/com/google/fhir/proxy/BearerAuthorizationInterceptor.java#L238

cc : @bashir2

Support extracting all patient IDs when URL contains ORs

Reported by @omarismail94

The proxy does not support having ORs in search parameters.

If I do a direct request to GCP FHIR Store, it works:

curl -X GET -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "Content-Type: application/json; charset=utf-8" \
  "https://healthcare.googleapis.com/v1alpha2/projects/[PROJECT]/locations/us/datasets/[DATASET]/fhirStores/gcs-data/fhir/Observation?_id=3f6cb1cf-2c0d-739d-0790-5ab8a26daf2e&subject=Patient/92a950eb-6aae-0e82-c297-d91585b22edf,performer=Patient/123"

If I send the same to proxy, I get an error saying that the patient id cannot be found.

Also we should turn any request that has searches that contain OR, e.g. subject=P1,P2 into an access check for both P1 AND P2, i.e. client should have access to P1 and P2.

Use HAPI client for FHIR API calls

The main job of the proxy is to relay FHIR calls from the client to the FHIR store server. But there are cases that a new FHIR API call is initiated by the proxy, e.g., to check existence of a patient, checking/patching access List resource, etc. For these cases, instead of handcrafting the query, we should use a FHIR library like HAPI FHIR client.

Reevaluate test infra options

We currently use Kokoro for our e2e test-infra. The main drawback of this is that it depends on some [Google] internal tools/config. We should evaluate other options like Cloud-Build or GitHub-Actions and possibly switch to one of those.

Implement location/role based AccessChecker

Any practical Healthcare solution needs some form of Role based access control and also limit the access based on the primary location, organization, careteam of a practitioner.

We should support a configurable access checker to ease onboarding process for our partners.

Blocked on

Support DELETE operations

We currently do not support DELETE operations in the access-checkers; we should fix this.

It's currently a P3 because it is not clear how important it is for the immediate use-cases we know of.

Support gzip encoding for data transfer

We should support "gzip" encoding for fetching responses from the FHIR resource server, as this is crucial towards optimising for network bandwidth. The client can tell the server that it can accept the "gzip" encoding with Accept-Encoding header value

Support fetch/mutate resources by ID

We currently have support for extracting patient IDs from a request URL (or a Bundle) and the idea is that the AccessChecker decides whether to grant access based on those patient IDs. One implication of this is that for example to fetch an Observation by ID, the client needs to provide the corresponding patient query parameter as well. To support directly fetch/mutate a resource by ID, we can first fetch that resource from the server, extract the patient from it (based on patient compartment) and then decide. This is a useful feature to be implemented in the shared library in the server module.

Header passing revisit

We should revisit what headers we pass to the FHIR store; in particular missing If-Match and ETag is currently a bug. In general, we should make sure all FHIR related headers are passed to the FHIR store or dropped for good reasons.

Clean up error messages to the client

Right now, in many error cases, we send large stack-traces to the client. This is probably of little value to the proxy user and the proxy developer should have access to the logs (which is were these traces are already printed and should be kept).

As a developer for OHS, it would be nice if the example / test different configurations of HAPI were the same.

We use HAPI in a few different situations - end-to-end testing (automatic & manual) and for illustrative purposes.

The one in Gateway is nice because it has data preloaded into it.
But, the one in Data-pipes is not as nice because you have to run the uploader. And we cannot use the one in Gateway because data-pipes depends on having Postgres as the underlying database.

Suggestion:

  • Look into changing the Gateway version to be PostGres, and then modify the instructions in Analytics to leverage that one instead.

Evaluate requirements/feasibility of supporting custom endpoints

The current architecture assumes that any request to the gateway is eventually passed to the FHIR server for returning a response. There are use-cases where we may want to support custom (non-FHIR) endpoints, e.g., in the context of sync as mentioned in #122. There are different ways to support this. We should first evaluate the requirements for this feature and make a conscious decision whether to support it. If the answer is yes, we should have a mini-design to close this issue.

Release new image for FHIR Gateway

While running through the fhir-app-examples instructions, William and I noticed that the instructions make references to the following docker images:

us-docker.pkg.dev/fhir-proxy-build/stable/fhir-access-proxy:latest

Two things here.

  1. We shoudl probably update the naming to be FHIR-gateway.
  2. When we checked this image, it looks like it was last built / released in mid-2022.
  3. Upon fixing the name / path, we should go back and also adjust the instructions back at the link at the top.

Formalize the release process

We need to have a clear recommendation on how to use the access-proxy core for users who develop their own AccessChecker plugin. As part of this exercise we should create our own release process, possibly including pushing binary artifacts to a binary repository. The users, should not need to fork the server code.

Clean up sample Keycloak tokens

Currently we keep many of the default claims in the sample Keycloak config. This is confusing to the users who try the gateway for the first time. We should clean-up these default tokens and remove most of the optional/unnecessary claims.

Make a new release 0.2.0

We have had several important features over the last few months and that warrants a new release; namely:

  • Improving SMART-on-FHIR support
  • Support for mutating the input query parameters
  • Improved AllowedQueriesChecker
  • Support for gzip responses

Request mutation for POST requests

We added support to mutate query params for GET Http method in #122

Currently there is no requirement for supporting mutates for the post body.
We intentionally wanted to restrict any random mutation to the payload by the gateway.

This might be required for Search by POST. We can think of supporting this after we fix #87

We should re-evaluate this in future based on the client requirements.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.