Skip to content

Latest commit

 

History

History
266 lines (194 loc) · 9.92 KB

README.md

File metadata and controls

266 lines (194 loc) · 9.92 KB

MLLP Adapter

The MLLP(Short for "Minimal Lower Layer Protocol") adapter is a component that runs on GKE, receives HL7v2 messages via MLLP/TCP, and forwards received messages to HL7v2 API.

Requirements

Initial Pull

The prebuilt docker image is staged on Google Container Registry (GCR). It is strongly recommended to use the prebuilt docker images:

docker pull gcr.io/cloud-healthcare-containers/mllp-adapter:latest

Note that the 'latest' image tag is convenient for development and testing, but production deployments should use a stable image tag to ensure that a new image isn't unintentionally deployed whenever the mllp-adapter image is updated.

Build (Optional)

We use bazel as the build tool. Please refer to the bazel documentation to get started.

Run the following commands to build the MLLP adapter binary:

cd mllp_adapter
bazel build :mllp_adapter

Or to build and load a docker image that contains the binary:

bazel run :mllp_adapter_docker

If docker is installed on your system, you should be able to see the image:

docker images

Test Locally

To run the image locally:

docker run -p 127.0.0.1:2575:2575 -v ~/.config:/root/.config gcr.io/cloud-healthcare-containers/mllp-adapter /usr/mllp_adapter/mllp_adapter --hl7_v2_project_id=<PROJECT_ID> --hl7_v2_location_id=<LOCATION_ID> --hl7_v2_dataset_id=<DATASET_ID> --hl7_v2_store_id=<STORE_ID> --export_stats=false --receiver_ip=0.0.0.0 --pubsub_project_id=<PUBSUB_PROJECT_ID> --pubsub_subscription=<PUBSUB_SUBSCRIPTION_ID>

In the command above:

  • -p 127.0.0.1:2575:2575 is used to publish the port 2575 of the MLLP container to host port 2575. By default MLLP adapter listen to port 2575;
  • -v ~/.config:/root/.config is used to give the container access to gcloud credentials;

Also note that:

  • PUBSUB_PROJECT_ID and PUBSUB_SUBSCRIPTION_ID are available by creating a pubsub topic and a subscription on Google Cloud;

You should be able to send HL7v2 messages now:

# This will fail because the format is invalid.
echo -n -e '\x0btestmessage\x1c\x0d' | telnet localhost 2575

NOTE: Older versions of the MLLP adapter subscribed to the single Pub/Sub topic configured in an HL7v2 store's notification_config field, and sent an outgoing message if a Pub/Sub notification had the "publish" attribute set (i.e. notification sent by the messages.create method). The new recommended method is to use the HL7v2 store's configurable notification_configs field to route messages to separate Pub/Sub topics and configure the MLLP adapter to subscribe to a topic that contains only messages it should send.

To continue using the old behavior, set the --legacy_publish_attribute=true flag when running the image. This functionality is deprecated and the publish attribute will be removed in a future release.

Deployment

Use Customized Service Account

If you want to use a custom service account instead of the default GCE service account, make sure the custom service account is passed as --service-account parameter while creating the cluster (See the doc).

The custom service account will require the following permissions to work properly: * roles/pubsub.subscriber. This is required only if you need the MLLP adapter to listen for pubsub notifications and forward the HL7v2 messages to HL7v2 stores. This role only needs to be set on a per-subscription basis. * roles/healthcare.hl7V2Ingest. * roles/monitoring.metricWriter. This is for writing metrics to Stackdriver. You don't need this if --exportStats is set to false.

Before deploying the docker image to GKE you need to publish the image to a registry. First modify BUILD.bazel to replace my-project and my-image with real values, then run:

bazel run :image_push

If this fails with the error message "ModuleNotFoundError: No module named 'urlparse'", you are hitting a python 3 bug in the google/containerregistry repo (google/containerregistry#42). Fix this by setting:

BAZEL_PYTHON=python2.7

If the push fails with "Permission denied", try running the following the command in the console and retry:

gcloud auth configure-docker

Next replace the placeholders in the deployment config for your use case.

Note that the <IMAGE_LABEL> should be a stable image tag that identifies the version of mllp-adapter image you want deployed. Do not use the 'latest' image tag, as this points to a new image each time there is an update.

You can retrieve the list of mllp-adapter image tags using the following command:

gcloud container images list-tags gcr.io/cloud-healthcare-containers/mllp-adapter

DIGEST        TAGS         TIMESTAMP
edea3487df8a               1969-12-31T19:00:00
231b073df13d  blue,latest  1969-12-31T19:00:00

In the above example, the image tag 'blue' will be the stable image tag for the latest mllp-adapter image at the time of execution.

Deploy to a GKE cluster:

# To use default service account.
gcloud container clusters create mllp-adapter --zone=<ZONE_ID> --scopes https://www.googleapis.com/auth/pubsub
# Or to use custom service account. Read the documentation here: https://cloud.google.com/sdk/gcloud/reference/container/clusters/create
gcloud container clusters create mllp-adapter --zone=<ZONE_ID> --service-account <SERVICE_ACCOUNT>

# See the documentation here: https://kubernetes.io/docs/reference/kubectl/kubectl/
kubectl create -f config/mllp_adapter.yaml
kubectl create -f config/mllp_adapter_service.yaml

Use Binary Authorization Service

Google Binary Authorization Service can be applied as a deploy-time security control to ensure only trusted container images can be deployed. Please refer to Enable Binary Authorization with MLLP Adapter Deployment for details of setup.

VPN

Use E2E VPN setup if want your data to be encrypted end-to-end. See the "encryption in transit" doc for more details.

Cloud VPN

Cloud VPN creates a secure tunnel to ensure data is encrypted in transit.

First create a static IP address for the VPN gateway (GATEWAY_IP):

gcloud --project <PROJECT_ID> compute addresses create <IP_NAME> --region=<REGION_ID>

Then set up the VPN:

gcloud --project <PROJECT_ID> compute target-vpn-gateways create mllp-vpn --region <REGION_ID> --network <VPC_NAME>
gcloud --project <PROJECT_ID> compute forwarding-rules create "mllp-vpn-rule-esp" --region <REGION_ID> --address <GATEWAY_IP> --ip-protocol "ESP" --target-vpn-gateway "mllp-vpn"
gcloud --project <PROJECT_ID> compute forwarding-rules create "mllp-vpn-rule-udp500" --region <REGION_ID> --address <GATEWAY_IP> --ip-protocol "UDP" --ports "500" --target-vpn-gateway "mllp-vpn"
gcloud --project <PROJECT_ID> compute forwarding-rules create "mllp-vpn-rule-udp4500" --region <REGION_ID> --address <GATEWAY_IP> --ip-protocol "UDP" --ports "4500" --target-vpn-gateway "mllp-vpn"

Next set up the tunnels (also in the opposite direction which isn't shown here):

gcloud --project <PROJECT_ID>  compute vpn-tunnels create "tunnel-1" --region <REGION_ID>  --peer-address <PEER_IP_ADDRESS> --shared-secret <SHARED_SECRET> --ike-version "2" --target-vpn-gateway "mllp-vpn" --local-traffic-selector="10.100.0.0/24"
gcloud --project <PROJECT_ID>  compute routes create "tunnel-1-route-1" --network <VPC_NAME> --next-hop-vpn-tunnel "tunnel-1" --next-hop-vpn-tunnel-region <REGION_ID> --destination-range "10.101.0.0/24"

Finally configure the firewall to allow traffic if necessary:

gcloud --project <PROJECT_ID> compute firewall-rules create allow-mllp-over-vpn --network <VPC_NAME> --allow tcp:32577,icmp --source-ranges "10.101.0.0/24"

E2E VPN

The docker VPN image used in this section is here on github. First check the configurations in the repo to see if you need to make any changes for your use case (you probabaly do).

Pull the image from docker hub and upload it to gcr.io:

# Build the image from source code instead of pulling it if you made changes in previous step.
docker pull philplckthun/strongswan
docker tag philplckthun/strongswan:latest gcr.io/<GCR_PROJECT_ID>/<IMAGE_NAME>:<IMAGE_LABEL>
gcloud docker -- push gcr.io/<GCR_PROJECT_ID>/<IMAGE_NAME>:<IMAGE_LABEL>

Replace the placeholders in the deployment config.

Load necessary kernel modules before applying the configuration changes. Then apply the changes:

kubectl apply -f config/mllp_adapter_e2e.yaml

(Optional) Allocate a static IP address for the load balancer:

gcloud --project <PROJECT_ID> compute addresses create vpn-load-balancer --region=us-central1

Update the service config to expose it as an external load balancer.

Apply the change as well by running kubectl apply -f config/mllp_adapter_service_e2e.yaml

This will also update the firewall rules automatically.

Now connect your client to the VPN server and test if you can access the adapter:

kubectl describe pods | grep IP: # Get pod IP.
echo -n -e '\x0btestmessage\x1c\x0d' | telnet <POD_IP> 2575

Debug

To view the running status and logs of the pod:

kubectl get pods
kubectl logs <POD_ID>