How to Integrate with AWS EC2 Web Service using REST API

Sourabh12
Tera Contributor

Currently trying to integrate with AWS EC2 web service using REST APIs. I have everything working in 'Postman' but when I try to implement the same headers(Authentication) within ServiceNow, I end up with 401 error.

Below is the code which I am using, can anyone suggest that I am using the correct values for variables : uri, CanonicalURI, CanonicalQueryString, CanonicalResource.

// SETTING VARABILES
var accessID = ''; // This come from AWS
var yourSecretAccessKeyID = ''; // This comes from AWS
var CanonicalResource = 'us-east-1'; // The file name that you want to retrive 
var region = 'us-east-1'; //region of bucket
var service = 'ec2'; // service type
var bucket = '<CHANGE ME>'; //bucket name



var uri = 'https://ec2.amazonaws.com/?';
var CanonicalURI = encodeURIComponent(uri);
var CanonicalQueryString = 'Action=StopInstances&Version=2016-11-15';
var CanonicalHeaders = '';
var SignedHeaders = '';

var digest = new GlideDigest();
var HashedPayload = digest.getSHA256Hex(''); //ServiceNow's method for SHA256 hex
var hash = HashedPayload.toLowerCase();
var date = getDate();
//var jshash = x_snc_key_master.CryptoJS.SHA256(''); // using the crypto js way to SHA256 hex
//var hash = jshash.toLowerCase(); // uncomment out if you use the crytpo js method


//Headers
var header1 = 'host';
var headValue1 = 'ec2.amazonaws.com';
var header2 = 'x-amz-date';
var headValue2 = hash;

// Date converter for AWS date format
function getDate() {
    var date; {
        var gdt = new GlideDateTime();
        var time = gdt.getTime();
        var t = time.getByFormat("HHmmss");
        var wd = (function() {
            var map = {
                '1': 'Mon',
                '2': 'Tue',
                '3': 'Wed',
                '4': 'Thu',
                '5': 'Fri',
                '6': 'Sat',
                '7': 'Sun'
            };
            return map[gdt.getDayOfWeekUTC() + ''];
        })();
        var d = (function() {
            var map = {
                '1': '01',
                '2': '02',
                '3': '03',
                '4': '04',
                '5': '05',
                '6': '06',
                '7': '07',
                '8': '08',
                '9': '09',
                '10': '10',
                '11': '11',
                '12': '12',
                '13': '13',
                '14': '14',
                '15': '15',
                '16': '16',
                '17': '17',
                '18': '18',
                '19': '19',
                '20': '20',
                '21': '21',
                '22': '22',
                '23': '23',
                '24': '24',
                '25': '25',
                '26': '26',
                '27': '27',
                '28': '28',
                '29': '29',
                '30': '30',
                '31': '31'
            };
            return map[gdt.getDayOfMonthUTC() + ''];
        })();

        var m = (function() {
            var map = {
                '1': '01',
                '2': '02',
                '3': '03',
                '4': '04',
                '5': '05',
                '6': '06',
                '7': '07',
                '8': '08',
                '9': '09',
                '10': '10',
                '11': '11',
                '12': '12'
            };
            return map[gdt.getMonthUTC() + ''];
        })();
        var y = gdt.getYearUTC();
        date = y + m + d + 'T' + t + 'Z';
    }
    return date;
}
// gs.print('date = ' + date);




//Task 1: Create a Canonical Request
//<HTTPMethod>\n
//<CanonicalURI>\n
//<CanonicalQueryString>\n
//<CanonicalHeaders>\n
//<SignedHeaders>\n
//<HashedPayload>


// Creating the Canonical Header
CanonicalHeaders = header1.toLowerCase() + ':' + headValue1.trim() + '\n' +
    header2.toLowerCase() + ':' + headValue2;

gs.print('CanonicalHeaders:' + CanonicalHeaders);

//Signed Headers
SignedHeaders = header1.toLowerCase() + ';' + header2.toLowerCase();

//Create CanonicalRequest
var CanonicalRequest = 'GET' + '\n' +
    uri + '\n' +
    CanonicalQueryString + '\n' +
    CanonicalHeaders + '\n' + '\n' +
    SignedHeaders + '\n' +
    hash;

gs.print('CanonicalRequest: ' + CanonicalRequest);

//Task 2: Create a String to Sign
//"AWS4-HMAC-SHA256" + "\n" +
//timeStampISO8601Format + "\n" +
//<Scope> + "\n" +
//Hex(SHA256Hash(<CanonicalRequest>))

// Setting Variables needed for String to Sign
var date_sub = date.substring(0, 8);
var scope = date_sub + '/' + region + '/' + service + '/' + 'aws4_request';
var hashCanonical = digest.getSHA256Hex(CanonicalRequest);
var hashLower = hashCanonical.toLowerCase();
//var jshashCanonical = x_snc_key_master.CryptoJS.SHA256(CanonicalRequest);// using the crypto js way to hex


var stringToSign = 'AWS4-HMAC-SHA256' + '\n' +
    date + '\n' +
    scope + '\n' +
    hashLower;

//gs.print('StringToSign: ' + stringToSign);



//Task 3: Calculate Signature

//DateKey              = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>")
//DateRegionKey        = HMAC-SHA256(<DateKey>, "<aws-region>")
//DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>")
//SigningKey           = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
//Signature 		   = HMAC-SHA256(SigningKey, StringToSign)


var signature = getSig(yourSecretAccessKeyID, date_sub, region, service);
//gs.print('Signature: ' + signature);

// This Function creates the encoding of the Signature	

function getSig(yourSecretAccessKeyID, date_sub, region, service) {
    try {
        var kSecret = yourSecretAccessKeyID;
		gs.log('@@ kSecret'+ kSecret);
		
		gs.log('@@ Before DateSub: '+ date_sub);
        var kDate = global.CryptoJS.HmacSHA256(date_sub, 'AWS4' + kSecret);
		gs.log('@@ After kDate : '+kDate);
		
		gs.log('@@ Before Region: '+ region);
        var kRegion = global.CryptoJS.HmacSHA256(region, kDate);
		gs.log('@@ After kRegion: '+ kRegion);
		
		gs.log('@@ Before Service: '+ service);
        var kService = global.CryptoJS.HmacSHA256(service, kRegion);
		gs.log('@@ After kService: '+ kService);
		
        var kSigning = global.CryptoJS.HmacSHA256('aws4_request', kService);
		gs.log('@@ After kSigning: '+ kSigning);
		
        var kSignature = global.CryptoJS.HmacSHA256(stringToSign, kSigning);
		gs.log('@@ After kSignature: '+ kSignature);

        //gs.print('kSignature: ' + kSignature);

        return kSignature;

    } catch (e) {
        gs.log(e.string());
        throw 'failed_missing_requirement';
    }
}

//Create Authorization String
var authorization = 'AWS4-HMAC-SHA256 Credential=' + accessID + '/' + scope + ', SignedHeaders=' + SignedHeaders + ', Signature=' + signature;
gs.log('@@ Signature : '+signature);
//gs.print('authorization: ' + authorization);
gs.log('@@ Authorization : '+authorization);

gs.log('@@ Date: '+date);

//Authorization header
getS3(authorization, date, headValue2);

// Creating the GET REST API 
function getS3(auth, date, headValue2) {
    try {
        var r = new sn_ws.RESTMessageV2();
        r.setHttpMethod('GET');
        r.setEndpoint('https://ec2.us-east-1.amazonaws.com/?Action=StopInstances&Version=2016-11-15&InstanceId=i-0cedd3f1defebd299'); // TAGET S3 BUCKET URL
		gs.log('@@ Auth : '+auth);
		gs.log('@@ Date : '+date);
        r.setRequestHeader('Authorization', auth);
        r.setRequestHeader('Date', date);
//         r.setRequestHeader('x-amz-date', headValue2);
        // 		r.saveResponseBodyAsAttachment('sys_data_source','<SYS_ID TO DATA SOURCE>','<NAME FILE YOU WANT ATTACHED>');
        var response = r.execute();

        var httpResponseStatus = response.getStatusCode() + '';

        gs.log("@@ http response status_code:  " + httpResponseStatus);

        if (httpResponseStatus != '200') {
            gs.log("@@ http response :  " + response.getBody());
        }

    } catch (ex) {
        var message = ex.message;
        gs.print(message);
    }


    //gs.print('This is the Body: ' + response.getBody()); // IF you do not get a code 200 , this might tell you whats wrong
    //gs.print('Status: ' + response.getStatusCode()); // code 200 is what you want

}
3 REPLIES 3

Ankur Bawiskar
Tera Patron
Tera Patron

Hi,

error 401 means authentication issue i.e. username and password is incorrect.

please compare the configuration changes of Postman and ServiceNow and check what is the difference

Regards
Ankur

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader

Miguel Donayre
ServiceNow Employee
ServiceNow Employee

Cool code, I wonder where you got it from?!?!?! You didn't read the part in my post where i mentioned that the only way for this to work is the CryptoJS library HAS TO BE IN A SCOOPED APPLICATION. It will not work if you put the CryptoJS at the Global level. THERE IS NO WAY AROUND IT. 

global.CryptoJS.HmacSHA256

Hi Miguel,

Thanks for your assistance. I created the CryptoJS library in Scopped App but still getting the 401 Error.

Please look into the attached code and let me know what could be the reason for this error. Also verify the Variables and it's values that I am using.

Thanks