In Apigee, how can I create a signed SAS token for Microsoft Azure Event hubs?

Microsoft Event Hubs allows Shared Access Signatures (SAS) tokens for accessing event hubs.

The token signing is described here:

https://docs.microsoft.com/en-us/azure/event-hubs/authenticate-shared-access-signature

It uses HMAC SHA256 as a signing algorithm.

The token structure is:

'SharedAccessSignature sr=' + encoded + 
       '&sig=' + encodeURIComponent(hash) + 
       '&se=' + ttl + 
       '&skn=' + saName;

where

field meaning
sr URI of the resource being accessed.
sig HMACSHA256 Signature computed over the resource URI (scope as described in the previous section) and the string representation of the token expiry instant, separated by a newline character (\n).
se Token expiry instant. Integer reflecting seconds since epoch
skn SAS key name

How can I create such a token within Apigee? I don't see any standard policy that would support that kind of signing.

Solved Solved
0 13 1,123
1 ACCEPTED SOLUTION

Brian, I think you can use this callout:

https://github.com/DinoChiesa/Apigee-Java-AzureEventHubs-SasToken

with this configuration:

<JavaCallout name='Java-GenerateSasToken-1'>
  <Properties>
    <Property name="resource-uri">contoso.servicebus.windows.net/hub1</Property>
    <Property name="expiry">7d</Property>
    <Property name="key">{private.shared_access_key}</Property>
    <Property name="key-name">{encodedkey_name}</Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.azureeventhubs.SasCallout</ClassName>
  <ResourceURL>java://apigee-azure-eventhubs-sas-callout-20191218.jar</ResourceURL>
</JavaCallout>

It worked for me with the test instance you gave me.

Some further comments.

"SAS" refers to Shared Access Signature, which is Microsoft's convention for applying HMAC, in other words a keyed-hash message authentication code, to produce a cryptographic signature for authentication purposes. HMAC is easy to compute in Apigee with the builtin features, like the HMAC policy and the hmac static function. But, the structure of Microsoft's token is not a simple encoded HMAC. Instead it is a series of parameters (sr, se, skn, sig) encoded in the x-www-form-urlencoded format.

An authorization header with a valid "token" looks like this:

Authorization:  SharedAccessSignature sr=https%3A%2F%2Fcontoso.servicebus.windows.net%2F&sig=aCmxWdfkSEOEEh7B8Ju3Wc32rxkuOcxK5YUFPaI%2BMCY%3D&se=1585172644&skn=key1

While producing an HMAC is relatively straightforward in Apigee just using the builtin hmac capabilities and AssignMessage, assembling and encoding all of the pieces required by Microsoft for a SAS token can be sort of tedious. Therefore, I built this callout to aid in the assembly. It's pretty simple. The core of the Java logic is:

  Mac hmac = Mac.getInstance("HmacSHA256");
  hmac.init(new SecretKeySpec(keyBytes, "HmacSHA256"));
  String stringToSign = URLEncoder.encode(resourceUri, "UTF-8") + "\n" + expiry;
  byte[] hmacBytes = hmac.doFinal(stringToSign.getBytes("UTF-8"));
  String hmacB64 = new String(base64Encoder.encode(hmacBytes), "UTF-8");


  String sasToken =
      String.format(
          "SharedAccessSignature sr=%s&sig=%s&se=%d&skn=%s",
          URLEncoder.encode(resourceUri, "UTF-8"),
          URLEncoder.encode(hmacB64, "UTF-8"),
          expiry,
          keyName);

View solution in original post

13 REPLIES 13

There is as yet, no builtin HMAC policy in Apigee.

There is an existing HMAC callout (a custom policy) that works nicely.

https://github.com/apigee/iloveapis2015-hmac-httpsignature

I recommend one of two options.

option 1: to using that callout to produce the HMAC, along with a small set of other policies like AssignMessage/AssignVariable and JavaScript, to produce the constituent pieces for the signature, and to assemble those pieces.

Option 2: write your own custom policy that produces SAS signatures. It would do all the necessary things including assembly of the pieces. Just one policy needed.

----

For option 1, what I mean by setting up the constituent pieces... you will need "seconds since epoch" for the expiry. Apigee has a context variable that provides milliseconds since epoch, but not one for seconds since epoch. Also you will want to add some time (24 hours? 7 days?) to the current time to get the expiry time. And then you need to encode the URI and concatenate all that together to get the signature base. You would best be able to do that in a JavaScript policy like this:

var now = Math.floor((new Date()).valueOf() / 1000);
var sevenDaysInSeconds = 7 * 86400;

context.setVariable('sas_expiry', String(now + sevenDaysInSeconds));
var encoded = encodeURIComponent(uri);
context.setVariable('sas_encodedURI', encoded);

var signatureBase = encoded + '\n' + ttl;
context.setVariable('sas_signatureBase', signatureBase);

policy to get the HMAC

<JavaCallout name='Java-CalcHmac-2'>
  <Properties>
    <Property name="key">{private.sas_key}</Property>
    <Property name="algorithm">SHA-256</Property>
    <Property name="string-to-sign">{sas_signatureBase}</Property>
    <Property name="debug">true</Property>
  </Properties>
  <ClassName>com.apigee.callout.hmac.HmacCreatorCallout</ClassName>
  <ResourceURL>java://apigee-hmac-edge-callout-1.0.4.jar</ResourceURL>
</JavaCallout>

JavaScript to encode the result of that HMAC (available in variable hmac.signature.b64)

var hmac = context.getVariable('hmac.signature.b64');
var encoded = encodeURIComponent(hmac);
context.setVariable('sas_encoded_sig', encoded);

policy to assemble the components.

<AssignMessage>
  <AssignVariable>
    <Name>sas_token</Name>
    <Template>SharedAccessSignature sr={sas_encodedURI}&sig={sas_encoded_sig}&se={sas_expiry}&skn={saName}</Template>
  </AssignVariable>
</AssignMessage>

That will work; you'd want to put them all in a SharedFlow to make it simpler to use.

Option 2 would do all of that stuff in Java. Not much more complicated than the original HMAC policy. You could start from that and extend it. Microsoft even provides some helpful Java code. I prefer this option because it's cleaner.

EDIT:

try this?

https://github.com/DinoChiesa/Apigee-Java-AzureEventHubs-SasToken

Dino - First thank you for taking this on and responding so quickly. I did not compile but imported your test proxy and took they SAS Token generated and tested calling a resource (Event Hub) using the Token. I verified I had all the setting correct, but get a 401 when using to service. To test my Hub I also have been using the Powershell script that MS provides to generate the Token and it works. Did you test this against an Azure service? I am wondering how we could collaborate on testing since I am sure others will get value from what you have provided and surprised Google has not created this as an OOO policy yet. Please advise, I have no problem setting up a test namespace and hub to work through this.

Hey Brian,

You're welcome, I'm glad to help.

No, unfortunately I did not test this against an Azure instance; I don't have one and I don't think I can get one. I knew it was a risk that this signature wouldn't be quite right. You got a 401 - what did you set the expiry to? 7 days?

It's also unfortunate that Microsoft does not provide a set of "test vectors" - a set of example output signatures, each one for a given combination of { resource URI, key, expiry}. If I had a set of test vectors I could include them in the testing suite to make sure the code was working correctly.

If you can provide to me a test instance of Azure Event Hubs, I'm glad to accept it and give it a try. Email at dchiesa@google. If not direct access to a test instance, then.... can you provide for me a set of sample signatures? They can even be expired.

I just want to be able to compare valid output that you got (for example from the powershell script) to the output my callout is generating. Or even provide for me the Powershell script (not a code snip, a working script). I can run that myself and compare results.

There are a couple places for wiggle room in the description that Microsoft provided. I just made some guesses. With better test data I can ensure that the output is correct and consistent.

Just an addendum - I compared the output of the callout I wrote with the output generated by the Java program Microsoft provided; the outputs are identical for a given time and expiry. Likewise to the output of the JavaScript program that Microsoft provided.

So I'm not sure what the problem might be that you're experiencing. More test data would be helpful in tracking this down. I'll be glad to iterate on this to get it right.

Maybe a suggestion: double check all the settings: the resource url and keyname and key.

just sent the e-mail, let me know if it goes to junk or get's blocked

I haven't seen it ... EDIT: got it now.

I've tried using a Javascript callout to generate the Token from the MS examples, but cannot get the Token correct because their example is using object classes that I cannot find the libs for. I hardcoded stuff just to get it working and used a Mock endpoint to return the header variables to compare the Token generated. So close and have a ticket into support, but maybe some folks are smarter than I can help. See attached azuresaskeyexamp-js.txt

I don't think you'll be able to do what you want in a JS callout because the crypto.createHmac method is not available in the JS callout in Apigee. That will work in nodejs, but not in an Apigee callout.

Brian, I think you can use this callout:

https://github.com/DinoChiesa/Apigee-Java-AzureEventHubs-SasToken

with this configuration:

<JavaCallout name='Java-GenerateSasToken-1'>
  <Properties>
    <Property name="resource-uri">contoso.servicebus.windows.net/hub1</Property>
    <Property name="expiry">7d</Property>
    <Property name="key">{private.shared_access_key}</Property>
    <Property name="key-name">{encodedkey_name}</Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.azureeventhubs.SasCallout</ClassName>
  <ResourceURL>java://apigee-azure-eventhubs-sas-callout-20191218.jar</ResourceURL>
</JavaCallout>

It worked for me with the test instance you gave me.

Some further comments.

"SAS" refers to Shared Access Signature, which is Microsoft's convention for applying HMAC, in other words a keyed-hash message authentication code, to produce a cryptographic signature for authentication purposes. HMAC is easy to compute in Apigee with the builtin features, like the HMAC policy and the hmac static function. But, the structure of Microsoft's token is not a simple encoded HMAC. Instead it is a series of parameters (sr, se, skn, sig) encoded in the x-www-form-urlencoded format.

An authorization header with a valid "token" looks like this:

Authorization:  SharedAccessSignature sr=https%3A%2F%2Fcontoso.servicebus.windows.net%2F&sig=aCmxWdfkSEOEEh7B8Ju3Wc32rxkuOcxK5YUFPaI%2BMCY%3D&se=1585172644&skn=key1

While producing an HMAC is relatively straightforward in Apigee just using the builtin hmac capabilities and AssignMessage, assembling and encoding all of the pieces required by Microsoft for a SAS token can be sort of tedious. Therefore, I built this callout to aid in the assembly. It's pretty simple. The core of the Java logic is:

  Mac hmac = Mac.getInstance("HmacSHA256");
  hmac.init(new SecretKeySpec(keyBytes, "HmacSHA256"));
  String stringToSign = URLEncoder.encode(resourceUri, "UTF-8") + "\n" + expiry;
  byte[] hmacBytes = hmac.doFinal(stringToSign.getBytes("UTF-8"));
  String hmacB64 = new String(base64Encoder.encode(hmacBytes), "UTF-8");


  String sasToken =
      String.format(
          "SharedAccessSignature sr=%s&sig=%s&se=%d&skn=%s",
          URLEncoder.encode(resourceUri, "UTF-8"),
          URLEncoder.encode(hmacB64, "UTF-8"),
          expiry,
          keyName);

I am getting error "Unable to insantiate com.google.apigee.edgecallouts.azureeventhubs.SasCallout" after refrring jar file from github link

Can you show your policy configuration,

and also show where the JAR resource is (maybe a screenshot showing it in the proxy)

I put our SAS Tokens in a Key Vault and the values of properties an Assign Message Policy to make it more generic and reusable. I also created a Route to call the http://httpbin.org/post Target to test the values that it is generating.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout name="Java-GenerateSasToken">
    <Properties>
        <Property name="resource-uri">{Azure-NameSpace}/{Hub-Name}</Property>
        <Property name="expiry">1d</Property>
        <Property name="debug">true</Property>
        <Property name="key">{KVM-SAS-Key}</Property>
        <Property name="key-name">{Policy-Name}</Property>
    </Properties>
    <ClassName>com.google.apigee.edgecallouts.azureeventhubs.SasCallout</ClassName>
    <ResourceURL>java://apigee-azure-eventhubs-sas-callout-20191218.jar</ResourceURL>
</JavaCallout>