API Management - Plays Well with Other Azure Services

In the first post of the series, we took a quick look at API management for layering a very basic authentication scheme over an existing service. That works great when you have an existing service API with an HTTP interface, but what do you do when your existing service listens on a queue? If it's an Azure Queue or a ServiceBus Queue which expose REST APIs, then you can layer a developer-friendly, strongly typed API over that generic queue API and as a side benefit get usage analytics, enforce call limits and user management beyond the Shared Access Signature Keys you get from the Azure Storage Service.

 

Let's look at the Azure Storage API for a second. The REST interface exposed by the Azure Storage Service authenticate based on a Shared Access Signature and the APIs are, as we would expect, generic queueing APIs. API management helps simplifying programming against these queues since we can "translate" the security approach based on shared keys to user accounts by caller of the API.

 

 

 

Machine generated alternative text:
APIs - 
Summary 
Christoph's Queue Abstraction 
Settings 
Operations 
Security 
Issues 
Products 
Operations 
Define service operations to enable service documentation, interactive API console, per operation 
and operation-level statistics. 
O ADD OPERATION 
POST 
GET 
Add Message 
Get Next Message

 

Each API operation defines a couple of policies below front ends Azure queues with an API management API. The "translation" is implemented via API operations with a pair of policies.

 

The inbound policy transforms the queue specific API call into an Azure queue REST request. The outbound policy in this example simply extracts the message from the XML-formatted queue message and returns it to the caller. The steps for front end a ServiceBus queue are very similar. I'll post a sample within the next few days.

 

The approach with policies is very straight forward. There are only a few things related to the Azure queue interface in the sample below. The Azure Queues API requires a signature over the headers of the HTTP request.  That signature is comprised of:

 

  • The standard HTTP headers
  • The time header
  • Canonical resource and canonical query parameters

 

All signed with the shared key for the storage account. In addition, the timestamp header in the signature has to match the actual header, which also has to be within 15 minutes of the current time to prevent replay attacks.

 

The <inbound> policy below shows how to create the signature and the timestamp headers.

 

<inbound>

<base />

<set-variable name="UTCNow" value="@(DateTime.Now.ToString("R"))" />

<set-header name="Authorization" exists-action="override">

<value>

@{

string accountName = "<your storage account name>";

string sharedKey = "<your key>";

string sig = "";

string canonical = String.Format(

"GET\n\n\n\nx-ms-date:{0}\n/{1}/myqueue/messages",

  context.Variables.GetValueOrDefault<string>("UTCNow"), accountName );

 

using (HMACSHA256 hmacSha256 = new HMACSHA256( Convert.FromBase64String(sharedKey) ))

{

                Byte[] dataToHmac = Encoding.UTF8.GetBytes(canonical);

                sig = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));

            }

 

return String.Format("SharedKey {0}:{1}", accountName, sig);

}                           

</value>

 

</set-header>

<set-header name="x-ms-date" exists-action="override">

<value>@( context.Variables.GetValueOrDefault<string>("UTCNow") )</value>

</set-header>

</inbound>

 

Note that you're not fully qualifying .NET types when referencing static methods or in variable declarations. If you do, you'd get an error, for example:

 

Usage of type 'System' is not supported within expressions.

 

Machine generated alternative text:
One or more fields contain incorrect values: 
Error in element •set-variable' on line O, column 0: Usage of type 'System' is not supported within expressions 
PO •cy 
I • (policies) 
(inbound) 
O FULL SCREEN 
@ DELETE POLICY PO •cy S 
o 
Alow c 
Authen 
(set-variable name: "UTCNow" 
(set-header name: "Authorization" exists-action: "override" 
(value)

 

The policy engine "know" about a subset of .NET types. The namespaces are already pre-imported so you don't have to reference any namespaces in your expressions.

 

To understand how to create the canonical header string, you can take a look at the code in the storage samples. However, it's pretty easy to troubleshoot problems with the Authorization header. A malformed header or a bad signature can manifest themselves as a 500 Internal Server error. Inspecting the trace data (ideally with a JSON formatter) you'll find that the error was a 403 Authentication Failed. (Note: Tracing is only available when you're logged into the developer portal with administrator rights).

 

Machine generated alternative text:
"outbound 
"source": "handler" , 
"timestamp 
"elapsed" 
"response 
"status 
"reason": "Server failed to authenticate the request _ 
Make sure the value of Authorization header is formed correctly including the signature _

 

In other cases, you'll find the directly in the API response, which you can test in the developer portal. Missing a header value in the signature causes signatures computed by the API management proxy and the Azure Storage service to be different. The error message returned from Azure Storage will show the canonical headers that Azure Storage expects. 

 

Machine generated alternative text:
is not the same as any computed signature. 
text/x ml 
x-ms-date:Tue, B2 Jun 2B15 GMT 
/inintestqueue/myqueue/messages ' 
4/ Authentic 
K/ Error) 
Serwer used following string to sign: 
'POST

 

The line breaks are extremely important.  Make sure the headers you're using in the policy to compute the signature match exactly the headers expected by Azure Storage.

 

The sample <outbound> policy doesn't do much at this point. An Azure queue message is an XML document with a number of metadata elements. This policy strips out all the metadata from the XML message and returns the message text only. The message text could be a JSON object to make it very simple for javascript clients to process the response.

 

<outbound>

<base />

<set-body>@(

Encoding.UTF8.GetString(

   Convert.FromBase64String(

      XDocument.Parse(

         context.Response.Body.As<string>())

      .Descendants("MessageText")

      .First()

      .Value)))</set-body>

</outbound>

 

We also could compose a response following a well-defined message format with a more complex .NET expression, but we'd still be able to leverage API management and not require standing up any infrastructure to deliver notifications to iSupport customers.

 

Hope this helps with crafting policies and exposing internal services securely via queues.

No Comments

Add a Comment

As it will appear on the website

Not displayed

Your website