Upload Attachments from ServiceNow to Azure Blob Storage

Alessandro22
Tera Contributor

Hello,
I created this code to be able to send a json file to my azure storage. When I try to generate the token with the generateSasToken function I get an undefined signature.
Can anyone help me with this?

 

(function() {
 
    var azureStorageAccountName = 'my_storage_name';
    var azureStorageAccountKey = 'my_storage_key';
    var azureStorageContainerName = 'my_container';
    var jsonFileName = 'incident_list.json';
   
    var incidentQuery = 'sys_updated_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()^ORDERBYnumber';
   
    var incidentGR = new GlideRecord('incident');
    incidentGR.addEncodedQuery(incidentQuery);
    incidentGR.setLimit(1);
    incidentGR.query();

    var incidentList = [];

    while (incidentGR.next()) {
        var incident = {
            "number": incidentGR.getValue('number'),
            "opened_at": incidentGR.getValue('opened_at'),
            "short_description": incidentGR.getValue('short_description'),
            "caller_id": incidentGR.getValue('caller_id'),
            "priority": incidentGR.getValue('priority'),
            "state": incidentGR.getValue('state'),
            "category": incidentGR.getValue('category'),
            "assignment_group": incidentGR.getValue('assignment_group'),
            "assigned_to": incidentGR.getValue('assigned_to')
        };
        incidentList.push(incident);
    }

    var jsonString = JSON.stringify(incidentList);
 
    var blobServiceEndpoint = 'https://' + azureStorageAccountName + '.blob.core.windows.net';
    var blobSasToken = generateSasToken(azureStorageAccountName, azureStorageAccountKey, azureStorageContainerName, jsonFileName);

    var blobUrl = blobServiceEndpoint + '/' + azureStorageContainerName + '/' + jsonFileName + blobSasToken;
    gs.info('Blob URL: ' + blobUrl);

    try {
        var httpRequest = new sn_ws.RESTMessageV2();
        httpRequest.setEndpoint(blobUrl);
        httpRequest.setHttpMethod('PUT');
        httpRequest.setRequestHeader('x-ms-blob-type', 'BlockBlob');
        httpRequest.setRequestBody(jsonString);
 
        var response = httpRequest.execute();
 
        var httpResponseStatus = response.getStatusCode();
        var httpResponseContentType = response.getHeader('Content-Type');
        var httpResponse = response.getBody();
 
    } catch (error) {
        gs.error('Error loading certificate: ' + error);
        gs.error('Stack trace: ' + error.getStackTrace());
    }
    
    function generateSasToken(accountName, accountKey, containerName, blobName) {

        var startDate = new Date();
        var expiryDate = new Date();
        startDate.setMinutes(startDate.getMinutes() - 5);
        expiryDate.setFullYear(expiryDate.getFullYear() + 1);

        var signedPermissions = 'rwdlac';
        var signedService = 'b';
        var signedResourceType = 'c';

        var signedStart = formatDate(startDate);
        var signedExpiry = formatDate(expiryDate);

        var canonicalizedResource = '/' + accountName + '/' + containerName + '/' + blobName;

        var stringToSign = signedPermissions + '\n' +
                           signedStart + '\n' +
                           signedExpiry + '\n' +
                           canonicalizedResource + '\n' +
                           '\n' +
                           signedService + '\n' +
                           signedResourceType;
       
       
        var hmac = new GlideDigest();
        hmac.setAlgorithm('HmacSHA256');
        var signature = hmac.base64Digest(accountKey, stringToSign);

        gs.info('String to sign: ' + stringToSign);
        gs.info('Generated Signature: ' + signature);
        gs.info('accountKey: --> ' + accountKey);

        var sasToken = 'sv=' + signedStart +
                       '&se=' + signedExpiry +
                       '&sr=' + signedResourceType +
                       '&sp=' + signedPermissions +
                       '&spr=' + signedService +
                       '&sig=' + encodeURIComponent(signature);

        gs.info('Token --> ' + sasToken);

        return '?' + sasToken;
    }
 
    function formatDate(date) {
        gs.info('date: ' + date);
        var formattedDate = date.toISOString();
        formattedDate = formattedDate.replace(/\.\d{3}Z$/, 'Z');
        return formattedDate;
    }
})();
5 REPLIES 5

Iraj Shaikh
Mega Sage
Mega Sage

Hi @Alessandro22 

The issue you're encountering with the `generateSasToken` function returning an undefined signature is likely due to the incorrect usage of the `GlideDigest` class for generating the HMAC-SHA256 signature. The `base64Digest` method of `GlideDigest` does not take two arguments (key and string to sign) as you are currently using it. Instead, you should be using the `getHMACBase64` method, which takes the key and the string to sign as arguments.

Here's how you can modify the `generateSasToken` function to correctly generate the signature:

 

function generateSasToken(accountName, accountKey, containerName, blobName) {
    // ... (other code remains the same)

    var stringToSign = signedPermissions + '\n' +
                       signedStart + '\n' +
                       signedExpiry + '\n' +
                       canonicalizedResource + '\n' +
                       '\n' +
                       signedService + '\n' +
                       signedResourceType + '\n' +
                       '\n' + // This line represents signed version, which is typically included in the string to sign
                       '\n' + // This line represents signed IP, which is typically empty in the string to sign
                       '\n' + // This line represents signed protocol, which is typically empty in the string to sign
                       '\n' + // This line represents signed identifier, which is typically empty in the string to sign
                       '\n';  // This line represents signed resource, which is typically empty in the string to sign

    var hmac = new GlideDigest();
    hmac.setAlgorithm('HmacSHA256');
    var signature = hmac.getHMACBase64(accountKey, stringToSign); // Correct method to generate HMAC-SHA256 signature

    gs.info('String to sign: ' + stringToSign);
    gs.info('Generated Signature: ' + signature);

    // ... (other code remains the same)

    return '?' + sasToken;
}

 


Please note that the `stringToSign` format should match the exact specification required by Azure for SAS token generation. The example above includes placeholders for parts of the string that are typically included in the string to sign but are empty in most cases. You should adjust these according to your specific requirements and Azure's documentation.

Additionally, ensure that the `accountKey` you are using is the base64-encoded key from your Azure Storage account. The `getHMACBase64` method expects the key to be in base64 format.

After making these changes, your `generateSasToken` function should correctly generate a SAS token with a valid signature.

Please mark this response as correct or helpful if it assisted you with your question.

Alessandro22
Tera Contributor

Hi Iraj Shaikh,


I entered the indicated changes, but the getHMACBase64 method does not work for me. Below is the error:

 

<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>

 

var stringToSign = signedPermissions + '\n' +
signedStart + '\n' +
signedExpiry + '\n' +
canonicalizedResource + '\n' +
'\n' +
signedService + '\n' +
signedResourceType + '\n' +
'\n' + // This line represents the signed version, which is typically included in the string to be signed
'\n' + // This line represents the signed IP, which is typically blank in the string to be signed
'\n' + // This line represents the signed protocol, which is typically blank in the string to be signed
'\n' + // This line represents the signed identifier, which is usually empty in the string to be signed
'\n'; // This line represents the signed resource, which is usually empty in the string to be signed

var hmac = new GlideDigest();
hmac.setAlgorithm('HmacSHA256');
var signature = hmac.getHMACBase64(accountKey, stringToSign);

 

the accountKey parameter is base64.

 

String to sign: racw
2024-01-23T16:44:57Z
2025-01-23T16:49:57Z
/my_storage/my_container/incident_list_20240123.json

b
c





 

Hi @Alessandro22 

The error message `<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>` suggests that the string to sign or the way the signature is being generated does not conform to Azure's expected format.

Here are a few things to check and correct:

1. String to Sign Format: Ensure that the string to sign is constructed exactly as Azure expects. The order of the fields and the presence of newlines (`\n`) are critical. The string to sign typically includes the following fields in this order:

- Permissions (e.g., `rwdlac`)
- Start time (e.g., `2024-01-23T16:44:57Z`)
- Expiry time (e.g., `2025-01-23T16:49:57Z`)
- Canonicalized resource (e.g., `/blob/my_storage/my_container/incident_list_20240123.json`)
- Account name (e.g., `my_storage`)
- Identifier (empty)
- IP (empty)
- Protocol (empty)
- Version (e.g., `2019-02-02`)

2. Base64 Encoding of the Account Key: The account key must be base64-decoded before being used to generate the HMAC signature. Azure expects the key to be in raw binary format when used for HMAC computation.

3. URL Encoding: The signature must be URL-encoded before being appended to the SAS token.

Here's a revised version of the `generateSasToken` function that addresses these points:

 

function generateSasToken(accountName, accountKey, containerName, blobName) {
    // ... (other code remains the same)

    var signedVersion = '2019-02-02'; // Use the appropriate service version

    var stringToSign = signedPermissions + '\n' +
                       signedStart + '\n' +
                       signedExpiry + '\n' +
                       canonicalizedResource + '\n' +
                       accountName + '\n' + // Account name should be included in the string to sign
                       '\n' + // Identifier
                       '\n' + // IP
                       '\n' + // Protocol
                       '\n' + // Version
                       '\n';  // Resource

    var hmac = new GlideDigest();
    hmac.setAlgorithm('HmacSHA256');
    var decodedKey = GlideStringUtil.base64Decode(accountKey); // Decode the base64 account key
    var signature = hmac.getHMACBase64(decodedKey, stringToSign); // Generate the HMAC signature

    gs.info('String to sign: ' + stringToSign);
    gs.info('Generated Signature: ' + signature);

    var sasToken = 'sv=' + signedVersion +
                   '&st=' + encodeURIComponent(signedStart) +
                   '&se=' + encodeURIComponent(signedExpiry) +
                   '&sr=' + signedResourceType +
                   '&sp=' + signedPermissions +
                   '&spr=' + signedService +
                   '&sig=' + encodeURIComponent(signature); // URL encode the signature

    gs.info('Token --> ' + sasToken);

    return '?' + sasToken;
}

 


Please note that the `signedVersion` should be set to the version of the Azure Blob Storage service you are using. The `GlideStringUtil.base64Decode` method is used to decode the base64-encoded account key before generating the signature.

Make sure to replace '2019-02-02' with the actual version of the Azure Storage service you are targeting. The version specifies the format of the SAS token and the string to sign.

After making these changes, your `generateSasToken` function should generate a well-formed signature that Azure can authenticate.

Please mark this response as correct or helpful if it assisted you with your question.

Hi Iraj,

thanks for your help but the code is still not working for me, the signature is always undefined.

Below is my code:

 

var fileDate = new GlideDate();
var jsonFileName = 'incident_list_' + fileDate.getByFormat('yyyyMMdd') + '.json';
var azureStorageAccountName = 'xxxxxxxxx';
var azureStorageAccountKey = 'xxxxxxxxxxxxxxxx';
var azureStorageContainerName = 'xxxxxxxx';

var blobSasToken = generateSasToken(azureStorageAccountName, azureStorageAccountKey, azureStorageContainerName, jsonFileName);

function generateSasToken(accountName, accountKey, containerName, blobName) {
    var startDate = new Date();
    var expiryDate = new Date();
    expiryDate.setHours(expiryDate.getHours() + 8);

    var signedPermissions = 'racwd'; //required
    var signedResource = 'b'; //required
    var signedVersion = '2022-11-02'; //required
    var signedStart = formatDate(startDate);
    var signedExpiry = formatDate(expiryDate); //required
    var signedIdentifier  = '',
        signedIP  = '',
        signedProtocol  = '',
        signedEncryptionScope  = '',
        signedSnapshotTime  = '';

    var canonicalizedResource = '/blob/' + accountName + '/' + containerName + '/' + blobName;

    var stringToSign = signedPermissions + "\n" +
                    signedStart + "\n" +
                    signedExpiry + "\n" +
                    canonicalizedResource + "\n" +
                    signedIdentifier + "\n" +
                    signedIP + "\n" +
                    signedProtocol + "\n" +
                    signedVersion + "\n" +
                    signedResource  + "\n" +
                    signedSnapshotTime + "\n" +
                    signedEncryptionScope + "\n" +
                    "\n" +
                    "\n" +
                    "\n" +
                    "\n";
                    
    var hmac = new GlideDigest();
    hmac.setAlgorithm('HmacSHA256');
    var decodedKey = new GlideStringUtil.base64Decode(accountKey); // Decode the base64 account key
    var signature = hmac.getHMACBase64(decodedKey, stringToSign); // Correct method to generate HMAC-SHA256 signature

    gs.info('String to sign: ' + stringToSign);
    gs.info('decodedKey: ' + decodedKey);
    gs.info('Generated Signature: ' + signature);

    var sasToken = 'sp=' + signedPermissions +
                    '&st=' + signedStart +
                    '&se=' + signedExpiry +
                    '&sv=' + signedVersion +
                    '&sr=' + signedResource +
                    '&sig=' + encodeURIComponent(signature); // URL encode the signature

    return '?' + sasToken;
}

function formatDate(date) {
    gs.info('date: ' + date);
    var formattedDate = date.toISOString();
    formattedDate = formattedDate.replace(/\.\d{3}Z$/, 'Z');
    return formattedDate;
}

 

Do you have any other suggestions?

 

Thanks,

Alessandro