Using AWS Temporary Security Credentials on EC2 with Node.js

Using AWS Temporary Security Credentials on EC2 with Node.js

Applications that run on an EC2 instance must include AWS credentials in their AWS API requests. You could have your developers store AWS credentials directly within the EC2 instance and allow applications in that instance to use those credentials. But developers would then have to manage the credentials and ensure that they securely pass the credentials to each instance and update each EC2 instance when it's time to rotate the credentials. That's a lot of additional work.

Instead, you can and should use an IAM role to manage temporary credentials for applications that run on an EC2 instance. When you use a role, you don't have to distribute long-term credentials to an EC2 instance. Instead, the role supplies temporary permissions that applications can use when they make calls to other AWS resources. When you launch an EC2 instance, you specify an IAM role to associate with the instance. Applications that run on the instance can then use the role-supplied temporary credentials to sign API requests.

Using an IAM Role to Grant Permissions to Applications Running on Amazon EC2 Instances

So that's what I'm going to do today from start to end after spending some time making sense of it.
The application will be a Node.js one and further to the link I'm also going to dynamically get the role name.

First let's install the official AWS SDK for Node.js:

    npm install aws-sdk --save

Our application will start off with something like the below. Normally we would also include the configuration that we pull from a file somewhere but we don't have to worry about managing credentials anymore if we use temporary credentials.

Typically I see people either:

  1. Just plainly include the keys into the code in a config file or
  2. Have it included as part of a CI pipeline.

We'll do the recommended method which is to use the temporary credentials attached to the EC2 instance.

    var AWS = require('aws-sdk');
    AWS.config.region = 'ap-southeast-2'; //Sydney
    AWS.config.apiVersion = '2012-05-04';

Getting EC2 Meta-data

Each EC2 instance has access to its meta-data through a special url regardless of whatever subnet its in. There is a public generic url but we'll just use the default one:

You can explore all sorts of meta-data by calling curl and moving up and down directories. Make sure to include the / at the end though.

The screenshot above shows us the entire temporary credentials. But to dynamically get the role name we can just call:

/latest/meta-data/iam/security-credentials/ and then use that for the full call.

Get the EC2 Rolename

The administrator can give us the rolename beforehand and we can save this in code.
By using this function though, it wouldn't matter later on if the admin changed the role name.

This can happen if the organization decided to reorganize resources and the like.

    function getEC2Rolename(AWS){
        var promise = new Promise((resolve,reject)=>{
            var metadata = new AWS.MetadataService();
                if(err) reject(err);
        return promise;

Now we grab the temporary security credentials from the EC2 instance. It comes back as a string so make sure we parse it as a JSON object.

    function getEC2Credentials(AWS,rolename){
        var promise = new Promise((resolve,reject)=>{
            var metadata = new AWS.MetadataService();
                if(err) reject(err);   
        return promise;

Finally we apply these credentials to our AWS object and make calls to whatever resources that role is allowed.

Keep in mind that there is an expiration date on the Token. This is much more secure but will require a little bit of code to refresh the temporary credentials when the token expires.

        return getEC2Credentials(AWS,rolename)

        AWS.config.sessionToken = credentials.Token;

Testing the Temporary Credentials

If the role has permissions to write to a bucket we can test out if the new credentials work with this code:

    var s3 = new AWS.S3({params:{Bucket: 'bucketname', Key: 'filename'}});
    var body = fs.createReadStream('file_to_upload');


I haven't tested this yet with Elastic Beanstalk but I was able to attach a role to one so I think it would.
If anyone knows the answer please add it to the comments.

Thanks for reading!