Signing AWS API requests is a requirement for making any API call to AWS programmatically except when you directly use the AWS CLI or one of the AWS SDKs. This is AWS way to identify who sends the request. You sign requests with your AWS access key, which consists of an access key ID and secret access key. All AWS services in all AWS Regions support SigV4, except Amazon SimpleDB which requires SigV2.
Signature Version 4 involves following key steps:
- Create a canonical request.
- Use the canonical request and additional metadata to create a string for signing.
- Derive a signing key from your AWS secret access key. Then use the signing key, and the string from the previous step, to create a signature.
- Add the resulting signature to the HTTP request in a header or as a query string parameter.
One need not to manually perform these steps of deriving a signing key and adding authentication information to a request if using AWS SDKs (including the SDK for Java, .NET, Python, Ruby, or JavaScript) as SDKs handles them implicitly.
However, you need to manually sign requests only if you are directly making HTTP or HTTPS requests; which is what we are exploring here when making API calls from Adobe Campaign to AWS. Below code takes care of that heavy lifting process of creating HTTP request header containing AWS SignV4 signature.
Prerequisite
The solution requires HMAC-SHA256 hashing, so you need to import Google CryptoJS library in Adobe campaign. Follow details on Using external javascript libraries in Adobe Campaign for information on how to import external library in Adobe Campaign.
JS script for signing AWS API calls
//Load Google CyptoJS library in Adobe Campaign JavaScript folder
loadLibrary('cus:crypto.js') ;
var access_key = '<your AWS access key>'
var secret_key = '<your AWS secret key>'
var region = '<region>';
var hostUrl = 'myhostdomain.s3.amazonaws.com';
var uriPath = '/';
var awsService = 'execute-api';
var httpMethod = 'POST'; //GET or POST
var content_type = 'application/x-amz-json-1.0';
var endpoint = 'https://' + hostUrl + uriPath;
// Payload would be empty for GET request and json string for POST request
var payload = '{"products": [{"sku": "xyz"}]}';
// ISO8601 date format to the specific format the AWS API
function getAmzDate(dateStr) {
var chars = [":","-"];
for (var i=0;i<chars.length;i++) {
while (dateStr.indexOf(chars[i]) != -1) {
dateStr = dateStr.replace(chars[i],"");
}
}
dateStr = dateStr.split(".")[0] + "Z";
return dateStr;
}
// Function returns the Signature Key, see AWS documentation for more details
function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate = CryptoJS.HmacSHA256(dateStamp, "AWS4" + key);
var kRegion = CryptoJS.HmacSHA256(regionName, kDate);
var kService = CryptoJS.HmacSHA256(serviceName, kRegion);
var kSigning = CryptoJS.HmacSHA256("aws4_request", kService);
return kSigning;
}
// Get the date formats needed to form our request
var amzDate = getAmzDate(new Date().toISOString());
var authDate = amzDate.split("T")[0];
// Get the SHA256 hash value for our payload
var hashedPayload = CryptoJS.SHA256(payload).toString();
// Step 1: Create our canonical request
var canonicalReq = httpMethod + '\n' +
uriPath + '\n' +
'\n' +
'host:' + hostUrl + '\n' +
'x-amz-content-sha256:' + hashedPayload + '\n' +
'x-amz-date:' + amzDate + '\n' +
'\n' +
'host;x-amz-content-sha256;x-amz-date' + '\n' +
hashedPayload;
// Hash the canonical request
var canonicalReqHash = CryptoJS.SHA256(canonicalReq).toString();
// Step 2: Form our String-to-Sign
var stringToSign = 'AWS4-HMAC-SHA256\n' +
amzDate + '\n' +
authDate+'/'+region+'/'+awsService+'/aws4_request\n'+
canonicalReqHash;
// Step 3: Get Signing Key
var signingKey = getSignatureKey(secret_key, authDate, region, awsService);
// Get Signing Signature
var signature = CryptoJS.enc.Hex.stringify(CryptoJS.HmacSHA256(stringToSign, signingKey));
// Step 4: Form our authorization header
var authString = 'AWS4-HMAC-SHA256 ' +
'Credential='+
access_key+'/'+
authDate+'/'+
region+'/'+
awsService+'/aws4_request,'+
'SignedHeaders=host;x-amz-content-sha256;x-amz-date,'+
'Signature='+signature;
logInfo("authorization >>" + authString);
//Creating HTTP request
var req = new HttpClientRequest(endpoint)
req.header["Authorization"] = authString;
req.header["Content-Type"] = content_type;
req.header["X-Amz-Date"] = amzDate;
req.header["x-amz-content-sha256"] = hashedPayload;
req.method = "POST";
req.body = payload;
try {
req.execute()
}
catch(e)
{
logInfo(e)
}
var response = req.response;
var content = response.body.toString(response.codePage);
// "content" variable contains HTTP response packet received from AWS
logInfo(content);
req.disconnect();
One can use this script in Javascript activity within Adobe Campaign workflows to GET or POST data from AWS.
This script is also available on Github location: https://github.com/SachinDhir/adobe-campaign
References:
- https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html
- https://code.google.com/p/crypto-js