Why AWS access and secret keys should not be in the codebase

Setting AWS.config.credentials is a bad practice. And it is also unnecessary.

Author's image
Tamás Sallai
5 mins

Hardcoding credentials

I've seen a few applications with hardcoded AWS credentials. Even though it is something that should not happen, I can see why this pattern emerges from time to time. When there is nobody with adequate experience with the cloud it is the easiest and the "just works" solution that ticks all checkboxes. And not until much later, just before the product goes to production will it surface (well, I've seen teams where it wouldn't have been a problem even then).

After all, the easiest thing to start moving to the cloud is for an admin to create an IAM user, assign Administrator privileges, generate an access key, and finally insert this small snippet of code so that everybody can start working:

// don't do this
AWS.config.credentials = new AWS.Credentials("<access key>", "<secret key>");

Every project is running late, and trivial security problems are easier to blame on unknown "hackers" than a missed deadline.

Don't succumb to this. At the very least, delete that line and use environment variables for this:

AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... node index.js

As simple as that, this actually remedies most of the problems hardcoding keys brings.

Why hardcoding is bad

First, by hardcoding your keys into the codebase you'll commit them to version control. This is a red flag and an indication of poor handling of secrets. On its own, it does not immediately pose a security risk but makes another one far-far more impactful. Accidentally exposing git history leading to production account compromise is not entirely unheard of.

Second, hardcoded keys get precedence over those set by other means, making your application unable to adapt to the environment. This is the problem people start noticing when they prepare a production environment and find out that they can not configure separate permissions. The worse part is when they don't bother with this and use the same keys for all workloads.

And lastly, as you can only hardcode permanent credentials that means you can not take advantage of the short-lived tokens most services support. Lambda will use a key that expires in a short time, as well as EC2 and ECS. Exposing these tokens brings a lot smaller problem than exposing a long-lived one.

Where keys should come from

In short: the environment. Environment variables are precisely for this scenario, and they can be different for production/staging/development.

Production environment

Running your code on a server, possibly serving your clients? Depending on where you put your code, there are a few scenarios.

You might be tempted to use an environment parser and set the keys from there:

// don't do this
AWS.config.credentials = new AWS.Credentials(
	process.env.AWS_ACCESS_KEY_ID,
	process.env.AWS.SECRET_ACCESS_KEY
);

The AWS SDK handles it internally and manually setting this makes it harder to use other means, like the credentials file.

AWS_SESSION_TOKEN

For short-lived temporary credentials, there is a third variable you need to set: the AWS_SESSION_TOKEN. It is easy to forget this as it is not present for IAM users, but required when a role is involved, for example, in a Lambda function.

Running on AWS

If your code is running on Lambda/EC2/ECS or another service that supports execution roles, you should use that. Just create a role with the necessary permissions and assign it to your function. Nothing else is needed, the runtime will make sure everything is set up for you.

Running outside AWS

If your code is running outside AWS then you can't take advantage of the execution role functionality. In this case, set these variables in the environment:

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN

The AWS_SESSION_TOKEN is present when you are using temporary credentials.

Local development

The easiest way is to run aws configure and set the keys there. Every invocation will use these credentials, even for the CLI.

Running this command writes the .aws/credentials file:

[default]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>

Profiles

If you need more than one set of keys, add named profiles:

[default]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>
    
[profile1]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>
    
[profile2]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>

To specify the active one set the AWS_PROFILE variable before running the app:

export AWS_PROFILE=profile1
node index.js

Pass environment variables directly

If you do not want to use the credentials file, you can also set the variables directly. Set these parameters:

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN (for a role)

And run your app using them:

export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=... # for a role
node index.js

Note that the values are likely to be visible in the shell history, so don't assume they are kept secret.

Region

Apart from the credentials, hardcoding a region can also lead to problems and it's also unnecessary. Services running on AWS use the same region they are deployed to. Explicitly specifying the region makes your code less portable.

To set the region, surprise!, use environment variables:

export AWS_REGION=eu-west-1
node index.js

There is another variable called AWS_DEFAULT_REGION, which the CLI is using. This can lead to problems as there is no indication which is using which, so it's safest to set both:

export AWS_REGION=eu-west-1
export AWS_DEFAULT_REGION="$AWS_REGION"

You can also specify the region in the credentials file, even for profiles:

[default]
region = eu-west-1

Conclusion

Access and secret keys, as well as the region, should not be hardcoded. It is hardly ever justified, it is against the official recommendations, and can lead to problems and vulnerabilities later down the road. Just remove all the AWS.config.credentials from your codebase and run aws configure to set up a local development environment. This simple practice makes sure you won't have problems when you go into production as you can have separate comfigurations for different environments.

May 28, 2019