3

I've been trying to get the Log Collector API working in a node.js Azure Function but am stuck on the 403/Forbidden error which indicates I'm not forming the authorization header correctly. The complete code is in a github repository here:

https://github.com/sportsmgmt-labs/Azure-Log-Analytics-Node-Function

The Data Collector API documentation is here:

https://learn.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api

The authorization header should be formatted as follows:

Authorization: SharedKey {WorkspaceID}:{Signature}

Where the signature is encoded/encrypted like this:

Base64(HMAC-SHA256(UTF8(StringToSign)))

Here is my code that is creating the authorization header:

var contentLength = Buffer.byteLength(req.body['log-entry'], 'utf8');

var authorization = 'POST\n' + contentLength + '\napplication/json\nx-ms-date:' + processingDate + '\n/api/logs';

// encode string using Base64(HMAC-SHA256(UTF8(StringToSign)))
authorization = crypto.createHmac('sha256', sharedKey).update(authorization.toString('utf8')).digest('base64');

authorization = 'Authorization: SharedKey ' + workspaceId + ':' + authorization;

The response from the server is:

{"Error":"InvalidAuthorization","Message":"An invalid scheme was specified in the Authorization header"}

Could someone please help me understand what I'm doing wrong? Thanks!

Edit: Here is the Python code to do this:

def build_signature(customer_id, shared_key, date, content_length, method, content_type, resource):
    x_headers = 'x-ms-date:' + date
    string_to_hash = method + "\n" + str(content_length) + "\n" + content_type + "\n" + x_headers + "\n" + resource
    bytes_to_hash = bytes(string_to_hash).encode('utf-8')  
    decoded_key = base64.b64decode(shared_key)
    encoded_hash = base64.b64encode(hmac.new(decoded_key, bytes_to_hash, digestmod=hashlib.sha256).digest())
    authorization = "SharedKey {}:{}".format(customer_id,encoded_hash)
    return authorization

...and the C# code:

    static void Main()
    {
        // Create a hash for the API signature
        var datestring = DateTime.UtcNow.ToString("r");
        string stringToHash = "POST\n" + json.Length + "\napplication/json\n" + "x-ms-date:" + datestring + "\n/api/logs";
        string hashedString = BuildSignature(stringToHash, sharedKey);
        string signature = "SharedKey " + customerId + ":" + hashedString;

        PostData(signature, datestring, json);
    }

    // Build the API signature
    public static string BuildSignature(string message, string secret)
    {
        var encoding = new System.Text.ASCIIEncoding();
        byte[] keyByte = Convert.FromBase64String(secret);
        byte[] messageBytes = encoding.GetBytes(message);
        using (var hmacsha256 = new HMACSHA256(keyByte))
        {
            byte[] hash = hmacsha256.ComputeHash(messageBytes);
            return Convert.ToBase64String(hash);
        }
    }
Graham
  • 7,431
  • 18
  • 59
  • 84

1 Answers1

4

You need to decode the shared key first. Please try changing the following lines of code:

authorization = crypto.createHmac('sha256', sharedKey).update(authorization.toString('utf8')).digest('base64');
authorization = 'Authorization: SharedKey ' + workspaceId + ':' + authorization;

to:

authorization = crypto.createHmac('sha256', new Buffer(sharedKey, 'base64')).update(authorization, 'utf-8').digest('base64');
var signature = 'SharedKey ' + workspaceId + ':' + authorization;

Then the request header will look like:

headers: {
    'content-type': 'application/json',
    'Authorization': signature,
    'Log-Type': <log_type>,
    'x-ms-date': processingDate
},

Node.js code sample

var request = require('request');
var crypto = require('crypto');

// Azure Log Analysis credentials
var workspaceId = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
var sharedKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

var apiVersion = '2016-04-01';
var processingDate = new Date().toUTCString();

var jsonData = [{
   "slot_ID": 12345,
    "ID": "5cdad72f-c848-4df0-8aaa-ffe033e75d57",
    "availability_Value": 100,
    "performance_Value": 6.954,
    "measurement_Name": "last_one_hour",
    "duration": 3600,
    "warning_Threshold": 0,
    "critical_Threshold": 0,
    "IsActive": "true"
},
{   
    "slot_ID": 67890,
    "ID": "b6bee458-fb65-492e-996d-61c4d7fbb942",
    "availability_Value": 100,
    "performance_Value": 3.379,
    "measurement_Name": "last_one_hour",
    "duration": 3600,
    "warning_Threshold": 0,
    "critical_Threshold": 0,
    "IsActive": "false"
}]

var body = JSON.stringify(jsonData);    

var contentLength = Buffer.byteLength(body, 'utf8');

var stringToSign = 'POST\n' + contentLength + '\napplication/json\nx-ms-date:' + processingDate + '\n/api/logs';
var signature = crypto.createHmac('sha256', new Buffer(sharedKey, 'base64')).update(stringToSign, 'utf-8').digest('base64');
var authorization = 'SharedKey ' + workspaceId + ':' + signature;

var headers = {
    "content-type": "application/json", 
    "Authorization": authorization,
    "Log-Type": 'WebMonitorTest',
    "x-ms-date": processingDate
};

var url = 'https://' + workspaceId + '.ods.opinsights.azure.com/api/logs?api-version=' + apiVersion;

request.post({url: url, headers: headers, body: body}, function (error, response, body) {
  console.log('error:', error); 
  console.log('statusCode:', response && response.statusCode); 
  console.log('body:', body); 
});
Aaron Chen
  • 9,835
  • 1
  • 16
  • 28
  • Thanks for looking at this, Aaron. I just changed the code and get this 403 error: {"Error":"InvalidAuthorization","Message":"An invalid scheme was specified in the Authorization header"} – Graham Jun 14 '17 at 03:39
  • Please also remove `Authorization: ` from *authorization* variable. – Aaron Chen Jun 14 '17 at 04:23
  • We also don't need the var keyword in the second line. Unfortunately I'm still getting 403 with that message. – Graham Jun 14 '17 at 04:36
  • Are you still getting "*An invalid scheme was specified in the Authorization header*"? – Aaron Chen Jun 14 '17 at 04:39
  • Could you share the full code you have written (include how you send HTTP request) with us? – Aaron Chen Jun 14 '17 at 04:44
  • Yes, check the github repository above for the entire Function. I have not updated it with your suggestions yet - only my Function. Would you like access to our Azure portal? – Graham Jun 14 '17 at 04:51
  • Here are my headers (with edited workspaceId for privacy): { 'content-type': 'application/json', Accept: 'application/json', 'x-ms-date': 'Wed, 14 Jun 2017 04:57:03 GMT', Authorization: 'SharedKey d3123b88-1d5c-4194-8824-68f2bbbcea24:CTONwl+MbV+lvqHTqT8MHY5fJBdUqbkLyoijWJBJN6A=', 'Log-Type': 'Log Function Test' } – Graham Jun 14 '17 at 04:59
  • https://feedback.azure.com/forums/267889-log-analytics/suggestions/19587211-reduce-the-complexity-of-the-data-collection-api-a :) – Graham Jun 14 '17 at 05:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146597/discussion-between-graham-and-aaron-chen-msft). – Graham Jun 14 '17 at 05:29
  • Thanks, Aaron. I'll update the github repository with working code. – Graham Jun 14 '17 at 13:52