Combining API Management with Azure Relays

As you might remember, in June I was at the Integrate 2018, doing a presentation called Exposing BizTalk to the World. It was my second time presenting on what became the premier conference on Microsoft Integration and it was a fantastic experience. It wouldn’t be fair to talk about Integrate without thanking Theta for sponsoring my trip and time of work and BizTalk360 for organizing a great event.

But why am I talking about this almost four months later? Apart from the shameless plug, during that presentation suggested a couple of ways to expose BizTalk endpoints. One of the options was using API Management to expose BizTalk receive locations, and the other was using Azure Relay to bypass firewall and securely expose BizTalk endpoints.

A couple of weeks ago, I’ve actually combine both technologies to securely expose a BizTalk endpoint. On this scenario, the client needed to create an API that would be exposed to partners, but wanted to reuse a series of BizTalk processes that were already implemented. As this was a pilot that should highlight the agility and fast time to market that can be achieved with the cloud, we didn’t have time to go through the process of exposing their environment through the firewall and whitelist API Management.So I thought, why not expose BizTalk via Azure WCF Relay, then secure Azure Relay with API Management? That would allow me to:

  • Expose BizTalk fast, without making changes in the firewall.
  • Create a rest API using json format that would be transformed to XML behind the scenes, hiding the XML implementation from the partners.
  • Allow caching and rate limiting each operation without having to implement all of this within BizTalk.
  • Secure the endpoints using different methods, like oAuth or API Keys, without having to implement it in BizTalk.

But for this idea to fly, I would need to make sure that the Azure WCF Relay was properly secured using RelayTokens. So the challenge would be to make the RelayToken security transparent to the end user, which means that I had to implement that as an API policy.

RelayToken Code

Researching the internet, I found this post by Michael Stephenson that showed how to generate a RelayToken. I adapted the code to be included as part of the inbound block of an API Management policy, which looked like this:

<set-variable name="resourceUri" value="@(context.Request.Url.ToString())" />
<set-variable name="accessKey" value="{{accessKey}}" />
<set-variable name="keyName" value="{{accessKeyName}}" />
<set-variable name="relaytoken" value="@{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
string expiry =  Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
string resourceUri = (string)context.Variables["resourceUri"];
string stringToSign = Uri.EscapeDataString (resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes((string)context.Variables["accessKey"]));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
string sasToken = String.Format("SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
Uri.EscapeDataString(resourceUri), Uri.EscapeDataString(signature), expiry, context.Variables["keyName"]);
return sasToken;
}" />

For this code we needed 3 parameters which are defined using the set-variable policy:

  • resouceUri – the resource that will be secured
  • accessKey – the value of the SAS key used to generate the RelayToken
  • keyName – the name of the SAS key used to generate the RelayToken

accessKey and keyName can be stored on API Management Name Values repository, which guarantees that the key is stored as a secret but is still accessible within a policy, by simply referenced it with the following format: {{namevalue}}.

Once the relayToken has been created it should be added as an authorization header using the set-header policy:

<set-header name="ServiceBusAuthorization" exists-action="override">
	<value>@((string)context.Variables["relaytoken"])</value>
</set-header>

Caching the RelayToken

Since the RelayToken have a configurable expiry time, it can be cached. Adding the caching policy, the policy looks like this:

<cache-lookup-value key="@("relaytoken")" variable-name="relaytoken" />
<choose>
	<when condition="@(!context.Variables.ContainsKey("relaytoken"))">
		<set-variable name="resourceUri" value="@(context.Request.Url.ToString())" />
		<set-variable name="accessKey" value="{{accessKey}}" />
		<set-variable name="keyName" value="{{accessKeyName}}" />
		<set-variable name="relaytoken" value="@{
	TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
	string expiry =  Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
	string resourceUri = (string)context.Variables["resourceUri"];
	string stringToSign = Uri.EscapeDataString (resourceUri) + "\n" + expiry;
	HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes((string)context.Variables["accessKey"]));
	string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
	string sasToken = String.Format("SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
		Uri.EscapeDataString(resourceUri), Uri.EscapeDataString(signature), expiry, context.Variables["keyName"]);
	return sasToken;
	}" />
		<cache-store-value key="relaytoken" value="@((string)context.Variables["relaytoken"])" duration="3600" />
	</when>
</choose>

In the code above, the cache-lookup policy tries to copy the relaytoken from the cache into a variable of the same name. If the cache doesn’t contain that value – either because has not being generated yet, or because it has expired – the generation code kicks in, adding the value to the relaytoken variable, which is also added to the cache using the cache-store-value policy.

Gotchas

When I first wrote this policy the token generation worked “perfectly”, but it didn’t generated a valid token, because resourceUri was not being populated properly. After scratching my head a bit I realized that the set-backendservice policy was being added at the end of the inbound policy when I set it up through the UI. Moving it to the top of the script solved the problem.

JSON to XML transformation

Although not related to the RelayToken code generation, another advantage of matching API Management and WCF Relays was the ability to expose a REST API based on json payload, and convert the request from JSON to XML and the responses back from XML to JSON. This can be done easily using the set-body policy, thanks to the liquid support. Here is an example of the policy:

<set-body template="liquid">
	<MyMessage>
		<id>{{body.itemid}}</id>
		<value>{{body.itemvalue}}</value>
	</MyMessage>
</set-body>

This policy would take a request with the following json format:

{
	"itemid": "1234",
	"itemvalue": "abc123"
}

And transform in the following in XML format:

<MyMessage>
	<id>1234</id>
	<value>abc123</value>
</MyMessage>

Toon Vanhoutte has a great post about using liquid with API Management, that you can find here.

In summary

Combining the power of API Management and Azure Relay allowed me to create a modern API that connected to my BizTalk backend service in a secure and efficient way, without having to make any changes to my infrastructure. This can be quite a powerful way to leverage from your existing BizTalk investment but still helping your business to have services available on the cloud.

You can find the complete policy on this gist.

 


Posted

in

, ,

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *