Issue with generating "AWS Signature Version 4" for REST requests in OOB ServiceNow.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-23-2022 06:41 PM
Hi, community, I need your help. I've spent a few days trying to get ServiceNow to generate the correct Signature for Amazon services (AWS), but seems like it doesn`t work.
Here's what I'm trying to do:
- I created the Authentication Algorithm for the signature as explaining here,
- I`ve created the AWS credentials as described in the same article, using the algorithm created earlier and data for test Amazon account:
access key: AKIDEXAMPLE
secret: wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
- Then I try to create a signature according to the test parameters that are specified in the Amazon programming guide and the signature that is generated by my script in ServiceNow doesn`t match the signature that appears in the Amazon examples.
Checking the notes from manual:
Note: Amazon V4 signature based authentication can also be used from Script background.
So use it in is my script:
var service = 'iam';
var region = 'us-east-1';
var credentialSID = '<SID of the my Credential record>';
var content = 'Action=ListUsers&Version=2010-05-08';
var gdt = new GlideDateTime("2015-08-30 12:36:00");
// Define the HttpRequestData object
var host = service + "." + region + ".amazonaws.com";
var endpoint = "https://" + host ;
var httpRequestData = new sn_auth.HttpRequestData();
var dateNum = gdt.getNumericValue();
httpRequestData.setEndpoint(endpoint);
httpRequestData.setHost(host);
httpRequestData.setRegion(region);
httpRequestData.setService(service);
httpRequestData.setHttpMethod('get');
httpRequestData.setDate(dateNum);
httpRequestData.setContent(content);
var credential = (new sn_cc.StandardCredentialsProvider()).getAuthCredentialByID(credentialSID);
// Create the RequestAuthAPI object and sign the request
var signingAPI = new sn_auth.RequestAuthAPI(httpRequestData, credential);
var signMessage = signingAPI.generateAuth();
gs.log("Status is: " + signMessage.getStatus());
//------------------------------------------------------------------------
var headerMap = signMessage.getHeaderMap();
for(var x in headerMap) {
var y = headerMap[x];
gs.log('HeaderMap[' + x + ']: ' + y);
}
var tmpDate = '20150830T123600Z';
var tmpAuth = 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7'
gs.log('ShouldBe[Authorization]: ' + tmpAuth);
gs.log('ShouldBe[X-Amz-Date]: ' + tmpDate);
Status is: SUCCESS
HeaderMap[Authorization]: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-date, Signature=bdb91ff1bb6546c3ea5bff7fc1463037b0a0879ab7e0942402ec90d0d128fda6
HeaderMap[X-Amz-Date]: 20150830T123600Z
ShouldBe[Authorization]: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
ShouldBe[X-Amz-Date]: 20150830T123600Z
And, of course any try to send the request with generated that way signature failed with response status 403:
<ErrorResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<Error>
<Type>Sender</Type>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message>
</Error>
<RequestId>f9ba7ffa-70a0-505e-9a16-250f2b0ab95b</RequestId>
</ErrorResponse>
The error is only in the signature Request, because if I manually substitute a signature from Postman in the same request in Snow (hardcoded in the script, yes) , Amazon accepts the requests.
The same incorrect result is reproduced with my personal credential for AWS, on different SNow instances and different versions of ServiceNow (Rome) which I compare with the Signature results obtained via Postman for the same requests.
Any thoughts?
Regards, Nikolay.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎06-29-2022 05:19 PM
Thanks the Support tip on using query parameters. This prompted me to new research which led to some success. I managed to generate the same signatures through the Python scripts and script in the ServiceNow for the simple GET request:
Python (only changed part:):
# ************* REQUEST VALUES *************
method = 'GET'
service = 'iam'
host = 'iam.amazonaws.com'
region = 'us-east-1'
endpoint = 'https://iam.amazonaws.com'
request_parameters = 'Action=ListUsers&Version=2010-05-08'
access_key = 'AKIDEXAMPLE'
secret_key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
# Create a date for headers and the credential string
#t = datetime.datetime.utcnow()
#20220626T213155Z
t = datetime.datetime(2022, 6, 26, 21, 31, 55)
amzdate = t.strftime('%Y%m%dT%H%M%SZ')
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
and ServiceNow server script:
var method = 'GET';
var service = 'iam';
var region = 'us-east-1';
var credentialSID = '<sys_id of you credential>';
//var content = 'Action=ListUsers&Version=2010-05-08';
//20220626T213155Z
var gdt = new GlideDateTime("2022-06-26 21:31:55");
// Define the HttpRequestData object
var host = service + ".amazonaws.com";
var endpoint = "https://" + host;
var httpRequestData = new sn_auth.HttpRequestData();
var dateNum = gdt.getNumericValue();
httpRequestData.setEndpoint(endpoint);
httpRequestData.setHost(host);
httpRequestData.setRegion(region);
httpRequestData.setService(service);
httpRequestData.setHttpMethod(method);
httpRequestData.setDate(dateNum);
//httpRequestData.setContent(content); //DO NOT USE 'content' for query params, use addQueryParam!!!
httpRequestData.addQueryParam('Action', 'ListUsers');
httpRequestData.addQueryParam('Version', '2010-05-08');
var credential = (new sn_cc.StandardCredentialsProvider()).getAuthCredentialByID(credentialSID);
// Create the RequestAuthAPI object and sign the request
var signingAPI = new sn_auth.RequestAuthAPI(httpRequestData, credential);
var signMessage = signingAPI.generateAuth();
gs.log("Status is: " + signMessage.getStatus());
//------------------------------------------------------------------------
var headerMap = signMessage.getHeaderMap();
for(var x in headerMap) {
var y = headerMap[x];
gs.log('HeaderMap[' + x + ']: ' + y);
}
var tmpDate = '20220626T213155Z';
var tmpAuth = 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20220626/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-date, Signature=d17811503d52fe2cf0a9aacb48b11ca76bd020ec8b7bcf825e8846eb7e4aa60a'
gs.log('Should Be[Authorization]: ' + tmpAuth);
gs.log('Should Be[X-Amz-Date]: ' + tmpDate);
After that, I started to deal with POST requests - they turned out to be somewhat more complicated, since the documentation (and examples) don`t really work and, as a result, don`t quite correspond to the final version. But after some torment, I managed to get them to work.
Here is an example of a POST request (I`ve highlighted some important places that, IMHO, are not obvious in the documentation or even contradict it):
var method = 'POST';
var service = '<service>';
var region = '<region>';
var credentialSID = '<sys_id of you credential>';
// Define the HttpRequestData object
var endpoint = "https://" + service + "." + region + ".amazonaws.com";
var httpRequestData = new sn_auth.HttpRequestData();
httpRequestData.setEndpoint(endpoint);
httpRequestData.setRegion(region);
httpRequestData.setService(service);
httpRequestData.setHttpMethod(method);
//httpRequestData.setContent(content); //DO NOT USE 'content' for query params, use addQueryParam instead!!!
httpRequestData.addHeader('x-amz-content-sha256' , ''); //IMPORTANT!!!
httpRequestData.addQueryParam('Action', 'Publish');
httpRequestData.addQueryParam('Message', 'TestMessage');
httpRequestData.addQueryParam('Subject', 'Test');
var credential = (new sn_cc.StandardCredentialsProvider()).getAuthCredentialByID(credentialSID);
// Create the RequestAuthAPI object and sign the request
var signingAPI = new sn_auth.RequestAuthAPI(httpRequestData, credential);
var signMessage = signingAPI.generateAuth();
signMessage.setDirective("header");
var restMessage = new sn_ws.RESTMessageV2();
restMessage.setHttpMethod(method);
restMessage.setLogLevel('all');
restMessage.setEndpoint(endpoint);
var queryMap = httpRequestData.getQueryParamMap(); // IMPORTANT- signed message doesn`t contain query params (why?) so we should copy them from initial request.
for(var x in queryMap) {
var y = queryMap[x];
gs.log('queryMap[' + x + ']: ' + y);
restMessage.setQueryParameter(x, y);
}
var headerMap = signMessage.getHeaderMap(); // Headers from signed request.
for(var x in headerMap) {
var y = headerMap[x];
gs.log('HeaderMap[' + x + ']: ' + y);
restMessage.setRequestHeader(x, y);
}
var response = restMessage.execute();
gs.info("status = " + response.getStatusCode());
gs.info("body = " + response.getBody());
Hope this helps someone.
Regards, Nikolay.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎11-17-2022 05:39 AM
@Nikolay Mikheev This is very helpful, however, I cannot seem to get this to work. I can get this to work in Postman just fine. I define the Region and Service Name and the request goes through just fine. However, ServiceNow will generate an Authorization token to use, but when I use it in a request it doesn't work, it returns
var method = 'GET';
var service = 'nsm-service';
var region = 'us-west-2';
var credentialSID = '6db9679347c39d10ff7ca904846d4341';
var gdt = new GlideDateTime();
// Define the HttpRequestData object
var host = "nsm-service-corp.us-west-2.amazonaws.com";
var endpoint = "https://" + host;
var httpRequestData = new sn_auth.HttpRequestData();
var dateNum = gdt.getNumericValue();
httpRequestData.setEndpoint(endpoint);
httpRequestData.setHost(host);
httpRequestData.setRegion(region);
httpRequestData.setService(service);
httpRequestData.setHttpMethod(method);
httpRequestData.setDate(dateNum);
// httpRequestData.addQueryParam('Action', 'ListUsers'); //we don't use any params
// httpRequestData.addQueryParam('Version', '2010-05-08'); //we don't use any params
var credential = (new sn_cc.StandardCredentialsProvider()).getAuthCredentialByID(credentialSID);
// Create the RequestAuthAPI object and sign the request
var signingAPI = new sn_auth.RequestAuthAPI(httpRequestData, credential);
var signMessage = signingAPI.generateAuth();
gs.log("Status is: " + signMessage.getStatus());
//------------------------------------------------------------------------
var headerMap = signMessage.getHeaderMap();
for(var x in headerMap) {
var y = headerMap[x];
gs.log('HeaderMap[' + x + ']: ' + y);
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎01-23-2023 01:23 PM
Sorry, bit late, but better late than never.
I see couple of potential placed for error in your code.
First of all name of service you use as parameter ("nsm-service") doesn`t match to service name in host string ("nsm-service-corp"). But it is the "host" parameter that is used to generate the AWS-signature.
Second place is that:
httpRequestData.addHeader('x-amz-content-sha256' , ''); //IMPORTANT!!!
And finally, in the examples I tried to describe in detail how I debugged it. There, for example, you can see how you can set your own date for requests. This is very useful for generating the same requests (with the same timestamp, which is also used in the AWS-signature) from different systems. Try generating a request from Postman or from Python script (where it already works) and the same request (with the same timestamp) from the ServiceNow and compare the whole https requests (headers, body) in the logs. At the moment when they are the same - everything will work for you.
Regards, Nikolay
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎01-10-2024 08:59 AM
hi @Nikolay Mikhee1 ,
I am facing the signature issue.
Script: status = 403
*** Script: body = {"message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\n\nThe Canonical String for this request should have....................................
Do you have any solution for that ?