Introduction
In a previous post, I wrote about the steps I followed to add Authentication (AuthN) to HAPI FHIR by utilising OAuth2 Proxy, Nginx and Keycloak
In this post, we'll look at an alternate solution that replaces OAuth2 Proxy and Nginx with APISIX.
APISIX
APISIX is an open source API Gateway that offers a wide range of features for managing and securing APIs. It supports dynamic configuration, flexible routing, and has a rich plugin ecosystem.
Docker Compose
Let's start by running APISIX in 'standalone' mode:
services:
apisix:
container_name: apisix
build:
context: ./services/apisix
dockerfile: Dockerfile
restart: unless-stopped
environment:
APISIX_STAND_ALONE: true
APISIX_SSL_CERT: /usr/local/apisix/conf/cert/cert.pem
APISIX_SSL_CERT_KEY: /usr/local/apisix/conf/cert/key.pem
ports:
- 80:9080
- 443:9443
...
volumes:
- '${PWD}/services/apisix/conf/config-standalone.yml:/usr/local/apisix/conf/config.yaml'
- '${PWD}/services/apisix/conf/apisix-standalone.yml:/usr/local/apisix/conf/apisix.yaml'
- '${PWD}/certs/cert.pem:/usr/local/apisix/conf/cert/cert.pem'
- '${PWD}/certs/key.pem:/usr/local/apisix/conf/cert/key.pem'
...
See: docker-compose-apisix.yml
In standalone mode only the APISIX data plane is deployed and settings are loaded from a YAML configuration file:
apisix:
...
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
Routes are declared in a file named apisix.yaml (apisix-standalone.yml)
:
routes:
- uri: /*
host: hapi-fhir.au.localhost
upstream:
nodes:
"hapi-fhir:8080": 1
type: roundrobin
...
#END
APISIX will reload the file if it detects any changes.
Enable TLS
You can enable TLS and ciphers via the the ssl
settings in config.yaml (config-standalone.yml)
:
apisix:
node_listen: 9080
enable_admin: false
enable_ipv6: false
ssl:
enable: true
listen_port: 9443
ssl_protocols: "TLSv1.2 TLSv1.3"
ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
And certs via the the ssls
settings in apisix.yaml (apisix-standalone.yml)
:
routes:
- uri: /*
host: hapi-fhir.au.localhost
upstream:
nodes:
"hapi-fhir:8080": 1
type: roundrobin
...
ssls:
-
cert: |
-----BEGIN CERTIFICATE-----
MIIEYzCCAsugAwIBAgIRAI09tHJ9h1MZ3Yjn/hxbCZowDQYJKoZIhvcNAQELBQAw
gY8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEyMDAGA1UECwwpcm9i
QFJvYnMtTWFjQm9vay1Qcm8ubG9jYWwgKFJvYiBGZXJndXNvbikxOTA3BgNVBAMM
MG1rY2VydCByb2JAUm9icy1NYWNCb29rLVByby5sb2NhbCAoUm9iIEZlcmd1c29u
KTAeFw0yNTAxMDcyMDA2NDhaFw0yNzA0MDcyMTA2NDhaMF0xJzAlBgNVBAoTHm1r
Y2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEyMDAGA1UECwwpcm9iQFJvYnMt
TWFjQm9vay1Qcm8ubG9jYWwgKFJvYiBGZXJndXNvbikwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCuWQhFkxQRb10Yxb94upW9LQ8KVXKs+4ujd3YH/OPX
C9dsJM4qu9lSUUTjhEFTfexO9uYSpS15vZeOAkXVDjMSBLeXVRq+cg2Q1K3nGPa3
rPrAmjavCbvf/9Pi8r459v9kjWuKLXspoY2v7biJz/az2JYTwF7s0QKUcbz0K3tk
Ke3DBBIBfaIuBGUbidIT7p6vZY9EW0an2YzhN/F3PuQn8Xl/rC2NUrwIba1KgmUk
29XT2MaC2pq6g/D7sJcITF1soHFqhRkuT53J4NRc5Q9v134LTSqEppu2RibwZ2wL
oa/NaCOB8VXihT9HrjN0AViH9n27oAuf59uVZU5aPK5ZAgMBAAGjazBpMA4GA1Ud
DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQsq7oj
ij5wNK5tI2xSxRrN6iU0KzAhBgNVHREEGjAYghZoYXBpLWZoaXIuYXUubG9jYWxo
b3N0MA0GCSqGSIb3DQEBCwUAA4IBgQCTPN8SExEx3zKuz8AcqvGn3DutM4CVo6VJ
q3btlOppHP7EfU1vQ1YUg3t41vt04OTUJYEb7jlHc9ZqMU2/b8YSJ6hTlDvs16j7
b7F/GiZclRyO4SL8HBdDhleOlz9Z0dVwex0Joz6WkwnXPv7djwOMG4o9GQqGbFzP
cxjHKi/GsebPaGOy5/liuCqC7/UNebCyu4on73WHLj3YcjSf9uLsp9Vaq8NtCx5k
IlmI5ocp9cdZ5vq2/zkwdTSrVtVWs6ZNrt6JhUGSkG6BoKaatzQAaO9hwq0tId3P
SRjD4fYydWbEMusCxFcf6l7Jix96IaSG60TMLE5nh02QF9rtP4PRZ7aGaj6SuHK4
yyApQj8TtHFAqWU6HDbK4x6jQE00vX7GU8Nnw5FxLD4Ns8/lrccxT1fItB9R+1ld
QJ9WIwXytm10XNJVvJhLqOltw4PwWnc/4N88xA0oZD99qMUcDkspRDgUd3G2PmzV
BRbCSva9ET7UbprFfCDT9mVXxbnjz2s=
-----END CERTIFICATE-----
key: |
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuWQhFkxQRb10Y
xb94upW9LQ8KVXKs+4ujd3YH/OPXC9dsJM4qu9lSUUTjhEFTfexO9uYSpS15vZeO
AkXVDjMSBLeXVRq+cg2Q1K3nGPa3rPrAmjavCbvf/9Pi8r459v9kjWuKLXspoY2v
7biJz/az2JYTwF7s0QKUcbz0K3tkKe3DBBIBfaIuBGUbidIT7p6vZY9EW0an2Yzh
N/F3PuQn8Xl/rC2NUrwIba1KgmUk29XT2MaC2pq6g/D7sJcITF1soHFqhRkuT53J
4NRc5Q9v134LTSqEppu2RibwZ2wLoa/NaCOB8VXihT9HrjN0AViH9n27oAuf59uV
ZU5aPK5ZAgMBAAECggEANsiRGdOSXbwhg7Q3awcuIAh1jmi1JPfRs+bIts/XA+6b
nUafZbwrGHui6t7W7BZIV7OrLbaraHKTmbLLIJxandHPooTCZ49NBfJeRpyIgcSf
8j9C6ZNkboljmg9uiKY9L+pkHUIXTkiOTfajouIvAeoPlls/OKigZ+apWgwDtMAX
SdQ7Yd2pc9LjJAYN7GYoAAR7hn4fbR6p8dITPL4+wne5gQtutltUZxttElaGpWLz
84wu5kIqx3IzmwUD2WuCOymy+3kJVzis4HKeYtwW7ENbmpUmTFkXAJTrVGgRqSSb
kD6oCREwESkpI1+t50HepTifDIHuLyWU1ap+za4OgQKBgQDTdZn5jk3h5F1WkNbZ
iZCLy70zWflE2GJR0bPCqZA5DuEo5+5zQe10r4xXfOLbcspg04h+zjC3/sTkA0RV
RtoXJi8S1FYeajL+xgpiqWyltrFQAELP5yRi/ClZuf4TO7lUyzpklwPmrZnzb8AI
lV9g7IaRzvskYfkCT6ay6y3HCQKBgQDTEkjAeL4pUIw68qkZ2d2HLtY5ZNL8FsVs
aEOQSvXFDunm8OmTiTdsFsjX04VLnTz2d9cEwdCyMYvrbX6B/upN2ZgkKCpR8b9P
TaMEeQY+4VYd9SbcVLFuhZQOpb4EM4rDWN9jGk0BYiEbrXRZQIU6HTSSF+sLjZis
py2G24+w0QKBgBHrm3rstmj4Y3icmbih0eAnCge6DkfpVpu8e9F5cUGEo0xGK40U
/zyuS+R2LvuOBNyj0KN+cd6F9sWkCTx43q6ri726xPma4mt4+RRXa1+31dsDyqW3
3vuMhyyVeJTEsPYgqvgvXCNGfw+EXu/bSNP794OP2PTCYMnzWhs7lwuRAoGAYVCk
yljhFBtXDDalUI3qXVFy47Ngs2msTHcl73kgJ2Lg5OFeT++L5gH7R8b2Rg6Q9PH7
6O2TUxUU9c7d7QGi9ZHFW6ZJHM7g7adV6dIC1yr9kYJeEGfcBqD/ymEQYs+AwuBO
3lpZ9rFPonsukZf11P1yJ4lvjTwTkEbj7rF8ZoECgYB+n5qGTY2KSm6vW3RSPDv/
U1EOATvg//2QY4kknaBfrRmZuYS+EdVfCHkuHcLoDts3hAew3DVdUV93IFEFxh6+
v9sFzlZr90aEFHZhd8MSTAD20XcvxLHMsYnbG3O1kNgCuVX0KosW5mR061akNasW
0j9vW0tRX0bJhGDbt9BoJA==
-----END PRIVATE KEY-----
snis:
- "hapi-fhir.au.localhost"
#END
OpenID Connect Plugin
APISIX can intercept requests to your application and redirect them to an Authorisation server that supports OAuth 2.0 and OpenID Connect.
For example:
routes:
- uri: /*
host: hapi-fhir.au.localhost
upstream:
nodes:
"hapi-fhir:8080": 1
type: roundrobin
plugins:
openid-connect:
bearer_only: false
client_id: ${{CLIENT_ID}}
client_secret: ${{CLIENT_SECRET}}
discovery: ${{PROTOCOL}}://${{KEYCLOAK_HOSTNAME}}:${{KEYCLOAK_PORT}}/realms/${{KEYCLOAK_REALM}}/.well-known/openid-configuration
realm: ${{KEYCLOAK_REALM}}
redirect_uri: ${{PROTOCOL}}://${{HAPI_FHIR_HOSTNAME}}/oauth2/callback
scope: ${{SCOPE}}
session:
secret: ${{COOKIE_SECRET}}
...
HAPI FHIR AU with Auth Starter Project
Follow the steps in the HAPI FHIR AU with Auth Starter Project's Quick Start guide to enable secure access to HAPI FHIR.
Navigate to:
https://hapi-fhir.au.localhost
You should see something like:
Enter your username (hey@rob-ferguson.me) and password (secret), then click the 'Sign In' button to sign in using the OpenID Connect (OIDC) Authorization Code flow.
Note: I followed the steps in Keycloak's Getting Started with Docker guide to create: a realm; a user; and a client. Keycloak will import the hapi-fhir-dev
realm (i.e., development-realm.json) when it starts up.
Your connection is secure:
Navigate to the OpenAPI UI for the HAPI FHIR R4 Server:
https://hapi-fhir.au.localhost/fhir
You should see something like:
Note: You can override the default FHIR Server Base URL, for example:
hapi:
fhir:
server_address: https://hapi-fhir.au.localhost/fhir
To stop the services:
docker compose -f docker-compose-apisix.yml stop
To remove the services:
docker compose -f docker-compose-apisix.yml down
To remove the data volume:
docker volume rm backend_postgres_data
Source Code
What's Next
In the next post, we'll take a look at adding support for SMART on FHIR to HAPI FHIR.
Source Code
References
System Hardening
- Australian Signals Directorate: Implementing Certificates, TLS, HTTPS and Opportunistic TLS
- Cloudflare docs: Cipher suites recommendations
HL7
- HL7: Implementation Guide
- HL7: FHIR NPM Packages
- AU Core: Publication (Version) History
- AU Core FHIR Implementation Guide: AU Core - 1.0.0-preview
- AU Core FHIR Implementation Guide: Testing FAQs
- Sparked AU Core Test Data Postman collection
SMART on FHIR
- HL7: SMART App Launch
- SMART Health IT: SMART on FHIR
SMART on FHIR - Standalone Launch
- Project Alvearie: SMART App Launch
- Project Alvearie: Keycloak extensions for FHIR
- Keycloak extensions for FHIR: Upgrade to the Quarkus-based distribution
- Keycloak discussion: Fine grained scope consent management
SMART on FHIR - EHR Launch
Keycloak
- Keycloak docs: Configuring Keycloak for production
- Keycloak docs: Configuring TLS
- Keycloak docs: Configuring trusted certificates
- Keycloak docs: Configuring the hostname
- Keycloak docs: Using a reverse proxy
- Keycloak docs: Running Keycloak in a container
- Keycloak docs: Migrating to the Quarkus distribution
- Keycloak docs: Upgrading Guide - 26.1.0
- Keycloak docs: Authorization Services Guide
Keycloak-based Development
- GitHub: Keycloak Project Example
- GitHub: Awesome Keycloak
Keycloak Support
- Google Group: Keycloak User
- Google Group: Keycloak Dev
APISIX
- APISIX docs: Deployment modes
- APISIX docs: SSL Protocol
- APISIX docs: Certificate
- APISIX docs: Plugins - OpenID Connect
HAPI FHIR
- HAPI FHIR: Website
- HAPI FHIR: Documentation
- Google Group: HAPI FHIR