Monday, 13 December 2021

Sending Client Certificates with an HTTP Sender in Azure

If you are running inside an Azure App Service and you need to send a client certificate with you message, you'll need to follow these steps. 

             1. To configure the location on the key vault in the config "KeyVault:VaultUri".  Set it with the URL of your Azure Vault

             2. Another app setting, set "WEBSITE_LOAD_USER_PROFILE" with the value of "1".  Without this you will get the error "The system cannot find the file specified"

             3. Add a certificate with a private key in the Azure vault.  You can generate one in the azure vault.

             4. Under the App Service settings/Identity/System Assigned, turn status to ON.  Copy Object ID

             5. In the Vault / Access Policies, Add Access Policy.  

             6. Add Secret Permissions (Get)

             7. Add Certificate Permissions (Get, List)

             8. Set the Principle to the App Service using the object ID you got at step 4.

             8.1 Click "Add", and then also click "Save".  "Add" doesn't save even though the window closes, and this caused me much frustration.

             9. In the workflow, configure the http sender to use the vaults name of the certificate as the thumbprint.

             10. This was a very helpfull page that enabled me to work all this out

 

Note:  Integration Host stores the certificates once they have been acquired in memory.  It costs money and performance to keep pulling the certificate from the vault.  If you update the certificate, don't forget to restart the app.

Monday, 6 December 2021

Create a self-signed Client Certificate for Mutual SSL authentication

Here are the instructions for creating a self-signed Client Certificate for Mutual SSL authentication.  These can be used to client authentication in HL7 Soup, Integration Host, and their deployments to AWS and AZURE.

  • Install Open SSL.  I got it from here: https://slproweb.com/products/Win32OpenSSL.html. It requires a $10 paypal donation.
  • Run Win64 OpenSSL Command Prompt from your start menu.
  • CD to the directory you will create your certificate
  • Execute the following commands.  You will want to run them one at a time, and they will ask you for details about your business.  Fill it all out:

openssl ecparam -name prime256v1 -genkey -noout -out client1.key

openssl req -new -sha256 -key client1.key -out client1.csr

openssl x509 -signkey client1.key -req -in client1.csr -out client1.crt -days 1000 -sha256

openssl pkcs12 -export -out client1.pfx -inkey client1.key -in client1.crt 

  • Double-Click the PFX to install it into your user certificates.

Very handy😊

Edit:

The Azure Certificate Vault also allows you to generate a certificate that has everything you need. It was very easy to do.  You can then export it as a PFX. 

Wednesday, 10 November 2021

OID for MSH-3

A lot of the state COVID reporting requires that the MSH-3 uses an OID in their HL7 for the sending application.  Yeah, I normally use a human-readable code as a preference as I think that's more helpful, but some states require it.

HL7 Soup OID is:

2.16.840.1.113883.2.18.83.1

Integration Host OID is:

2.16.840.1.113883.2.18.83.2

So you end up with a message that looks like

MSH|^~\&|HL7Soup^2.16.840.1.113883.2.18.83.2^ISO|Instance1



Thursday, 4 November 2021

Updating a database for each OBX in HL7

The other day I needed to loop over each OBX segment in my message, and call an INSERT statement (or Stored Procedure) in my database. I know there are many ways to do this, but this was so simple that I thought it was worth sharing.

Obviously, in Integration Host (HL7 Soup), the trick is to build up a variable inside a ForEach transformer loop.  With each iteration, append the variable with another INSERT statement.  Add a semicolon at the end of your line to separate this query from the previous.


Note how I am setting the Query variable with its value first.  This builds up the text with each iteration.

Now you just head over to your Database Query activity and insert (right-click option) your variable as the Query Text.

How super simple was that! All done in a couple of minutes.

The only thing to watch out for is that this would be open to a SQL Injection, so only do it for controlled/sanitised data that doesn't allow Mr Johnny Drop Tables to crash your system.

It's all very much like this tutorial if you want help getting going.

Thursday, 16 September 2021

Looping over repeating OBX values and updating the segment

I regularly need to update HL7 messages as the data flows between systems. Often, it's significantly cheaper to take the feed designed for another integration and massage the HL7 message before passing it onto your integration.  

With Integration Host you generally just remap using the Transformers and it's all very simple. But the other day I got a wee bit tangled when trying to update OBX values.

I wanted to loop over each OBX segment, look at the value in the OBX-3.4, then use some conditions and lookup tables (note: loving lookup tables right now) to alter the value before inserting it back into the OBX-3.2.

So I used a FOREACH transformer to loop over the OBX values, then put the OBX-3.4 into a variable that I then updated to the value I needed.  This was all easy, but the trick came when I tried to write the value back into the same OBX line the FOREACH was on.  My mapping just wrote each value into the first segment, overwriting it each time, leaving the last in place. This stumped me for a bit and I contacted HL7 Soup support.

It turns out that the loop is only looping over the incoming OBX values, not the outgoing ones, so you can’t use it to set the value in the destination message.  Rather you need to create a different HL7 path for each iteration and set that value.  Apparently, they looked at creating a feature in the UI to allow this several times, but it turns out that code is by far the easiest way:

//Update the OBX-3.2 on the same index as the for each loop.
IHL7Message destinationMessage = (IHL7Message)activityInstance.Message;
string path = $"OBX[{workflowInstance.GetVariable("ForEachIterator")}]-3.2";
destinationMessage.SetValueAtPath(path,  workflowInstance.GetVariable("UpdatedValueVariable"));

Above, you can see that you just construct the path we wish to write to using the forEachIterator variable (which is automatically populated by the FOREACH Transformer), then set the path in the destination message with the needed value - the variable "UpdatedValueVariable" in my case.

Extracting HL7 Fields out of File Names with HL7 Soup

Sometimes when generating an HL7 message, the trigger or source of the message is a file that doesn't contain parsable data such as a PDF.  It's a fairly common practice to use the 255 filename characters to include some additional metadata.  This post shows how to access the filename and then how to extract the values.

Integration Host makes accessing the filename very easy.  If you have a Directory scanner the filename is automatically put into a variable for you to use "DirectoryScannerFileName". The value includes the filename and extension, so it's pretty convenient to use if you just want to update the same file, but as we want to use the values, we need to remove the extension and split that metadata out.

To do this I used a Code Transformer with the following code:

//Get the destination message
IHL7Message destinationMessage = (IHL7Message)activityInstance.Message; 

//Get the filename
string fileName = workflowInstance.GetVariable("DirectoryScannerFileName");

//Remove the extension
fileName = System.IO.Path.GetFileNameWithoutExtension(fileName);

//split it by the _ character
string[] values = fileName.Split('_');

//Write the values to their HL7 location.  I've just put them in dummy locations for now, as you can change them
//Note, that to use this the filename must have the correct number of values or you will get an error.
destinationMessage.SetValueAtPath("PV1-19", values[0]);
destinationMessage.SetValueAtPath("PV1-20", values[1]);
destinationMessage.SetValueAtPath("PV1-21", values[2]);

It's pretty straightforward.  You can see that I get the variable with the filename, then remove the extension, followed by splitting it by the "_" character.

Then it's just a matter of placing each value where I want it.  I've put it into the PV1-19 to 21, but you can use whichever location you need.

The one thing to note is that I've written this to error if the number of fields required isn't provided.  I prefer this outcome rather than proceeding even if the filename doesn't have all the values, but others may wish to put an IF condition around it to only add the values if the right number of fields exist.