Perhaps I'm going a bridge to far here but heres what I got:
An AWS API Gateway Method that has AWS_IAM set for Authorization.
A Policy that allows access to that Method.
An EC2 Role that has that policy attached to it.
An EC2 Launched with that Role.
I would like to have my NodeJS program (or any language for that matter) on that EC2 to be able to call that API without hardcoding an AccessKey and SecretKey in the code.
I have used this approach to use the aws-sdk to put/get records on S3, and do other AWS functionality (like all the steps I mentioned above), However, invoking an API Gateway seems to be outside the aws-sdk scope.
Calling the API with Wreck (the NPM I use from my HTTP calls in my app) and no headers results in:
{
"message": "Missing Authentication Token"
}
Not a big shock there.
Anything obvious I am missing?
So it appears you need to access your EC2 at http://169.254.169.254/latest/meta-data/iam/security-credentials/Role_Name
As explained here.
Here is my final code including Signing AWS Requests with Signature Version 4:
var Moment = require('moment');
var Wreck = require('wreck');
var Crypto = require('crypto');
function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate = Crypto.createHmac('sha256', 'AWS4' + key).update(dateStamp,'utf8').digest();
var kRegion = Crypto.createHmac('sha256', kDate).update(regionName, 'utf8').digest();
var kService = Crypto.createHmac('sha256', kRegion).update(serviceName, 'utf8').digest();
var kSigning = Crypto.createHmac('sha256', kService).update('aws4_request', 'utf8').digest();
return kSigning;
}
var assumed_role = 'MY_ROLE';
Wreck.get('http://169.254.169.254/latest/meta-data/iam/security-credentials/' + assumed_role, function(err, res, payload) {
var payload_obj = JSON.parse(payload.toString());
var access_key = payload_obj.AccessKeyId;
var secret_key = payload_obj.SecretAccessKey;
var token = payload_obj.Token;
var payload = {}
payload.email = 'devin.stewart#example.com';
payload.first_name = 'Devin';
payload.last_name = 'Stewart';
payload.full_name = 'Devin Stewart';
var request_parameters = JSON.stringify(payload);
var method = 'POST';
var api_id = 'MY_API_ID'
var service = 'execute-api';
var region = 'us-east-1';
var api_path = '/production/people';
var host = api_id + '.' + service + '.' + region + '.amazonaws.com';
var endpoint = 'https://' + host + api_path;
var content_type = 'application/json';
var t = Moment.utc()
var amz_date = t.format('YYYYMMDD[T]HHmmss[Z]');
var date_stamp = t.format('YYYYMMDD'); // Date w/o time, used in credential scope
var canonical_querystring = '';
var canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + amz_date + '\n' + 'x-amz-security-token:' + token + '\n';
var signed_headers = 'content-type;host;x-amz-date;x-amz-security-token';
var payload_hash = Crypto.createHash('sha256').update(request_parameters).digest('hex');
var canonical_request = method + '\n' + api_path + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash;
var algorithm = 'AWS4-HMAC-SHA256';
var credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request';
var string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + Crypto.createHash('sha256').update(canonical_request).digest('hex');
var signing_key = getSignatureKey(secret_key, date_stamp, region, service);
var signature = Crypto.createHmac('sha256', signing_key).update(string_to_sign, 'utf8').digest('hex');
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature;
var headers = {
'Content-Type':content_type,
'X-Amz-Date':amz_date,
'X-Amz-Security-Token':token,
'Authorization':authorization_header
};
var options = {headers: headers, payload: request_parameters};
Wreck.post(endpoint, options, function (err, res, payload) {
if (err) {
console.log(err.data.payload.toString());
} else {
console.log(payload.toString());
}
});
});
Related
I'm using postman for an api request. My question is; I'm getting an error when I made one of the variables random.
For example when I sent request this link;
https://testrest.xxxxxx.com/voucher/purchase/testAUD000/49/1/30000017/1 it is working. But when I change with this;
https://testrest.xxxxxx.com/voucher/purchase/testAUD000/49/1/30000017/{{transactionId}}
I'm getting this error; "You are not authorized to access this resource"
In my collection pre-request script;
var apiKey = pm.collectionVariables.get("API-KEY");
var apiSecret = pm.collectionVariables.get("API-SECRET");
var nonce = _.random(1, 50000000);
var signature = pm.request.method + "\n" + pm.request.url.getPath() + "\n" + nonce + "\n";
var hash = CryptoJS.HmacSHA256(signature, apiSecret).toString();
pm.request.headers.add("AUTHENTICATION: HMAC " + apiKey + ":" + hash + ":" + nonce);
I added this in collection pre-request script or request in pre-request script I'm getting error;
var random = Math.floor(Math.random() * 10);
pm.variables.set('transactionId',random)
I am trying to fetch a jpg image from AWS S3 Bucket using a HTTP GET request in React-Native.
So far I was following these 2 documentations from Amazon :
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
Here is the logic I implemented so far.
When my component render, I am doing the following :
import { Auth } from 'aws-amplify';
import Moment from 'moment';
import sha256 from 'crypto-js/sha256';
var CryptoJS = require("crypto-js");
...
Auth.currentCredentials()
.then(async credentials => {
let awsHost = 'https://myAwsHost.amazonaws.com'
let awsPath = '/public/pathToMyImage.jpg'
let accessKeyId = credentials.accessKeyId
let secretAccessKey = credentials.secretAccessKey
let sessionToken = credentials.sessionToken
let awsDate = Moment().utc().format("YYYYMMDD")
let awsRequestDateTime = Moment().utc().format("YYYYMMDD" + "T" + "HHmmss") + "Z"
let awsRegion = "myRegion"
let awsService = "s3"
let awsRequest = "aws4_request"
let awsAlgorithm = "AWS4-HMAC-SHA256"
let awsCredentialScope = awsDate + "/" + awsRegion + "/" + awsService + "/" + awsRequest
let awsHTTPRequestMethod = "GET"
let awsSignedHeaders = "host;x-amz-content-sha256;x-amz-date;x-amz-security-token"
let awsCanonicalURI = awsHost + awsPath
let awsCanonicalQueryString = ""
// Step 1
let canonicalRequest =
awsHTTPRequestMethod + '\n' +
awsCanonicalURI + '\n' +
awsCanonicalQueryString + '\n' +
"X-Amz-Content-Sha256".toLowerCase() + ":" + sha256("") + "\n" +
"X-Amz-Date".toLowerCase() + ":" + awsRequestDateTime.trim() + "\n" +
"X-Amz-Security-Token".toLowerCase() + ":" + sessionToken + "\n" +
awsSignedHeaders + '\n' +
sha256("")
// Step 2 :
let stringToSign =
awsAlgorithm + "\n" +
awsRequestDateTime + "\n" +
awsCredentialScope + "\n" +
sha256(canonicalRequest)
// Step 3 :
let kSecret = secretAccessKey
let kDate = CryptoJS.HmacSHA256("AWS4" + kSecret, awsDate)
let kRegion = CryptoJS.HmacSHA256(kDate, awsRegion)
let kService = CryptoJS.HmacSHA256(kRegion, awsService)
let kSigning = CryptoJS.HmacSHA256(kService, awsRequest).toString(CryptoJS.enc.Hex)
let awsSignature = CryptoJS.HmacSHA256(awsDerivedSignInKey, stringToSign).toString(CryptoJS.enc.Hex)
let awsAuthorization = awsAlgorithm + " Credential=" + accessKeyId + "/" + awsCredentialScope + ", SignedHeaders=" + awsSignedHeaders + ", Signature=" + awsSignature
// Fetching the image with an HTTP GET request to AWS S3 Buckets
try {
await fetch(
awsCanonicalURI,
{
headers: {
'X-Amz-Security-Token': sessionToken,
'X-Amz-Content-Sha256': sha256("").toString(),
'X-Amz-Date': awsRequestDateTime,
'Authorization': awsAuthorization
},
}
).then(res => {
console.log("HTML status : " + res.status) // Status return : Error 403
});
} catch (error) {
console.error(error);
}
})
}
When I am trying to execute the same GET request with postman it work's and it retrieve the image from AWS S3 Buckets.
The only difference between my request and the one generated by postman is the Signature.
I know that I can fetch images from S3Image component from aws-amplify-react-native but it is not what I am trying to achieve.
Main goal
Finally, I am looking to execute those HTTP GET Request's in order to use them in FastImage from the module react-native-fast-image and use it in order to cache easely images in my react-native application.
If anyone has an answer to my problem or a better alternative to what I am trying to achieve, be my guest!!
If it works in postman, You can get the Axios code sample for that using postman. Checkout the gif below.
I have s3 bucket as below,
myBucket
a/
b/
c/
where myBucket is a s3 bucket and a, b, c are the key folders inside that bucket.
I will upload images into a/. The s3 event notification will trigger SQS which will then trigger lambda function which does the process of removing image background and uploads into b/ folder.
The problem here is for example, if I upload a folder which has around 26 images into s3 only 23 or 22 images are getting triggered by lambda and only those images are getting are processed.
For some reason s3 is not triggering all the images or is that something I should configure in my lambda function?
Here is my function code
exports.handler = async(event, context, callback) => {
try {
console.log(event.Records[0]);
var json = JSON.parse(event.Records[0]['body']);
console.log('json: '+json);
json = JSON.parse(json['Message']);
json = json['Records'][0]['s3'];
console.log(json);
var srcBucket = json['bucket']['name'];
console.log('srcBucket: ' + srcBucket);
var srcKey = decodeURIComponent(json['object']['key'].replace(/\+/g, ' '));
console.log('srcKey: ' + srcKey);
var str = (srcKey.split('/').pop()).split('.')[0];
console.log('str: ' + str);
if (str != '') {
var folderPath = srcKey.substr(srcKey.indexOf('/') + 1).split('.')[0];
folderPath = folderPath.substring(0, folderPath.lastIndexOf('/'));
console.log('folderPath: ' + folderPath);
const params1 = { Bucket: srcBucket, Key: srcKey };
var origimage = await s3.getObject(params1).promise();
var destObject = await origimage.Body;
var destKey = 'removebg/' + folderPath + '/' + str + '.jpg';
var options = {
'method': 'POST',
'url': 'https://api.remove.bg/v1.0/removebg',
'headers': {
'X-Api-Key': 'xxxxxxxxxxx'
},
formData: {
'image_file': destObject,
'size': 'auto'
},
encoding: null
};
request(options, function(error, response, body) {
if (error) {
console.log(error);
sendmessage(error, 'Error removing image background', arn, srcBucket + '/' + srcKey, destBucket + destKey);
}
var params = { Bucket: destBucket, Key: destKey, Body: body };
s3.upload(params, function(err, data) {
if (err) {
console.log('Error uploading data: ', err);
sendmessage(err, 'Error uploading transparent image to s3', arn, srcBucket + '/' + srcKey, destBucket + destKey);
}
else { console.log('Successfully uploaded data to ' + destBucket); }
});
});
}
}
catch (e) {
console.log(e);
}
callback(null, 'All done!');
};
Please let me know. Thanks in advance.
I think your problem lies on these lines:
console.log(event.Records[0]);
var json = JSON.parse(event.Records[0]['body']);
console.log('json: '+json);
json = JSON.parse(json['Message']);
json = json['Records'][0]['s3'];
Your function is only looking at the first record that is provided to the function. Multiple events can be given the the Lambda function, so your function should loop through the Records entries and process all of the events that are provided.
It should do something like:
for record in event.Records:
console.log(record);
var json = JSON.parse(record['body']);
console.log('json: '+json);
json = JSON.parse(json['Message']);
json = record['s3'];
I'm trying to create the final step of this example in Flutter and I can't get it right for some reason:
https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
Using their Signing Key + String to sign they get this resulting signature:
5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
Using the exact same key / string to sign I end up with:
fe52b221b5173b501c9863cec59554224072ca34c1c827ec5fb8a257f97637b1
Here is my code:
var testSigninKey =
'c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9';
var testStringToSign = 'AWS4-HMAC-SHA256' + '\n' +
'20150830T123600Z' + '\n' +
'20150830/us-east-1/iam/aws4_request' + '\n' +
'f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59';
var hmac1 = Hmac(sha256, utf8.encode(testSigninKey));
var digest1 = hmac1.convert(utf8.encode(testStringToSign));
print(digest1);
print(hex.encode(digestBytes.bytes));
//both print: fe52b221b5173b501c9863cec59554224072ca34c1c827ec5fb8a257f97637b1
The hmac1 calls expects you to use the signing key as is, not to use an encoded version of the hex string representation of it.
You should be able to properly construct the Hmac object with this:
var hmac1 = Hmac(sha256, hex.decode(testSigninKey));
Here is a complete example of my flutter code, referring to the s3 signature creation example:
https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
I know it's not perfect but it took me extremely long to figure it out. Maybe it helps someone.
import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
class AWS {
var _algorithm = ["X-Amz-Algorithm", "AWS4-HMAC-SHA256"];
final String _awsService = "s3";
final String _aws4Request = "aws4_request";
var _expires = ["X-Amz-Expires", "86400"];
var _signedHeaders = ["X-Amz-SignedHeaders", "host"];
var testbucket = "https://s3.amazonaws.com/examplebucket";
var testfilePath = "/test.txt";
var _testDate = ["X-Amz-Date", "20130524T000000Z"];
var _testDateymd = 20130524;
var _testRegion = "us-east-1";
var _testCredential = [
"X-Amz-Credential",
"AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request"
];
var testSecretkey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
testString() {
var httpMethod = "GET";
var canUri = testfilePath;
var canQueryStr = Uri.encodeQueryComponent(_algorithm[0]) +
"=" +
Uri.encodeQueryComponent(_algorithm[1]) +
"&" +
Uri.encodeQueryComponent(_testCredential[0]) +
"=" +
Uri.encodeQueryComponent(_testCredential[1]) +
"&" +
Uri.encodeQueryComponent(_testDate[0]) +
"=" +
Uri.encodeQueryComponent(_testDate[1]) +
"&" +
Uri.encodeQueryComponent(_expires[0]) +
"=" +
Uri.encodeQueryComponent(_expires[1]) +
"&" +
Uri.encodeQueryComponent(_signedHeaders[0]) +
"=" +
Uri.encodeQueryComponent(_signedHeaders[1]);
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
var _canonicalRequest = httpMethod +
"\n" +
canUri +
"\n" +
canQueryStr +
"\n" +
"host:examplebucket.s3.amazonaws.com" +
"\n" +
"\n" +
"host" +
"\n" +
"UNSIGNED-PAYLOAD";
var bytes = utf8.encode(_canonicalRequest);
var _stringToSign = "AWS4-HMAC-SHA256" +
"\n" +
"20130524T000000Z" +
"\n" +
"20130524/us-east-1/s3/aws4_request" +
"\n" +
sha256.convert(bytes).toString();
print("String to sign: $_stringToSign");
// HEX und HMAC signature
List<int> _dateKey = utf8.encode("AWS4" + testSecretkey);
List<int> _dateMsg = utf8.encode(_testDateymd.toString());
Hmac dateHmac = Hmac(sha256, _dateKey);
Digest dateDigest = dateHmac.convert(_dateMsg);
String _dateRegionKey = dateDigest.toString();
List<int> _dateRegionMsg = utf8.encode(_testRegion.toString());
Hmac dateRegionHmac = Hmac(sha256, hex.decode(_dateRegionKey.toString()));
Digest dateRegionDigest = dateRegionHmac.convert(_dateRegionMsg);
String _dateRegionServiceKey = dateRegionDigest.toString();
List<int> _dateRegionServiceMsg = utf8.encode(_awsService.toString());
Hmac dateRegionServiceHmac =
Hmac(sha256, hex.decode(_dateRegionServiceKey.toString()));
Digest dateRegionServiceDigest =
dateRegionServiceHmac.convert(_dateRegionServiceMsg);
String _signingKey = dateRegionServiceDigest.toString();
List<int> _signingKeyMsg = utf8.encode(_aws4Request.toString());
Hmac _signingKeyHmac = Hmac(sha256, hex.decode(_signingKey.toString()));
Digest _signingKeyDigest = _signingKeyHmac.convert(_signingKeyMsg);
print("signing key: $_signingKeyDigest");
var _signatureKey = _signingKeyDigest.toString();
List<int> _signatureKeyMsg = utf8.encode(_stringToSign);
Hmac _signatureHmac = Hmac(sha256, hex.decode(_signatureKey));
var _signature = _signatureHmac.convert(_signatureKeyMsg);
print("Signature: $_signature");
}
}
I'm trying to generate the signature in Google Scripts without much luck.
My code is as follows:
function getAwsData(){
var AWS_SECRET = '[AWS_SECRET]';
var expiresDt = Math.floor(Date.now() / 1000) + (60 * 60 * 24);
var path = '/MYFOLDER/ACTIVATION.csv'
var stringToSign = 'GET\n\n\n' + expiresDt + '\n' + path;
var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, AWS_SECRET,stringToSign, Utilities.Charset.UTF_8);
var signature = encodeURIComponent(Utilities.base64Encode(hmac));
var response = UrlFetchApp.fetch("http://s3-ap-southeast-2.amazonaws.com/MYFOLDER/ACTIVATION.csv?AWSAccessKeyId=[AWS_ACCESS_ID]&Expires="+expiresDt+"&Signature="+signature);
var csvData = Utilities.parseCsv(response, ",");
console.log(csvData);
}
Can someone please see where I've gone wrong? I've followed the document here: http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html
It appears that I'm signing the hmac incorrectly.
var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, stringToSign, AWS_SECRET,Utilities.Charset.UTF_8);