Environment variables in Lambda functions

Environment variables are important building blocks in developing NodeJS code. Lambda functions can also work with environment variables. In this post, I'll briefly discuss how they can be set up and how they are used when the function is invoked.

NodeJS environment variables are often used in a daily basis.

And if they are used, then sooner or later (but rather sooner) we have to deal with them when writing Lambda function code.

Luckily, environment variables are easy to reason with when it comes to Lambda.

1. What are environment variables?

Environment variables are available as a key in the process.env object. For example, the database URL environment variable can be accessed as process.env.DATABASE_URL, in case, of course, we named it so. It’s a convention to name them in uppercase letter and use underscore if they consist of multiple words.

Environment variables are very useful in at least two cases.

First, if you don’t want to hardcode your secrets, for example, when you set up a secret token, you can create a placeholder like process.env.SECRET where the value of SECRET can be fetched from some really secret place, like the Parameter Store or the Secrets Manager. If these services cannot be used for some reason, a .env (dotenv) file will do, but make sure you don’t commit it to GitHub.

This way, the value of SECRET will be automatically injected in the code, and we don’t have to bake it into the code.

The other very frequent use case is when we develop for multiple environments (always in commercial projects). For example, DATABASE_URL can be localhost in the development environment, but it’s usually a real database in staging or production. In this case, configuration files may contain the values of the variable (except production, it’s not a good idea to hard-code it), or, similarly to the above situation, some secret storage can hold the values.

2. Environment variables in Lambda

Environment variables can also be used with Lambda functions.

Assume we have the following simple Lambda function in the file called index.js, which receives the name and the mission of an agent. These pieces of information must be kept in secret:

exports.handler = async (event) => {
  const agent = process.env.AGENT_NAME
  const mission = process.env.AGENT_MISSION

  return {
    agent,
    mission,
  }
}

As it can be seen, the use of environment variables in Lambda is no different from using them in an every-day NodeJS code.

Here comes the question: How can we declare the values of the environment variables?

Let’s go over the process through an example and deploy the function above.

I wrote a post on deploying Lambda functions using the CLI earlier, so I won’t repeat everything here.

After the zip file and the role are created, we can deploy the function with the following CLI command:

aws lambda create-function --function-name secret-agents-in-environment \\
sch--runtime nodejs10.x --role arn:aws:iam::123456789123:role/LambdaBasicExecutionRole \\
--environment Variables='{AGENT_NAME="James Bond", AGENT_MISSION="Get pendrive with agent list"}' \\
--handler index.handler --zip-file fileb://secret-agents.zip \\
--region ap-southeast-2

The explanation of the above command is as follows.

function-name will undoubtedly be the name of the Lambda function we’ll see in the Lambda console. role is the name of the basic execution role each Lambda function should have. zip-file is the name of the compressed file, which resulted from the zip secret-agents.zip index.js command. Of course, these names can all be different.

The environment variables are defined in the --environment option. As it can be seen, it’s an object, where the values are in quotes because they consist of more than one word.

If the execution of the above command was successful, we should see a response similar to this:

{
  "FunctionName": "secret-agents-in-environment",
  "FunctionArn":"arn:aws:lambda:ap-southeast-2:123456789123:function:secret-agents-inenvironment",
  "Runtime": "nodejs10.x",
  "Role": "arn:aws:iam::123456789123:role/LambdaBasicExecutionRole",
  "Handler": "index.handler",
  "CodeSize": 286,
  "Description": "",
  "Timeout": 3,
  "MemorySize": 128,
  "LastModified": "2019-09-16T19:52:49.528+0000",
  "CodeSha256": "kvi4q5BWoNMBTJyHQHux2CFYUX6elLSAvZcIekbatEc=",
  "Version": "$LATEST",
  "Environment": {
      "Variables": {
          "AGENT_MISSION": "Get pendrive with agent list",
          "AGENT_NAME": "James Bond"
      }
  },
  "TracingConfig": {
      "Mode": "PassThrough"
  },
  "RevisionId": "168e157a-fd84-4f4d-8c2d-6a997ce6e5ba"
}

This is what we can see in the console:

Environment variables in lambda
James Bond as environment variable

It looks good, so let’s quickly invoke the function:

aws lambda invoke --function-name secret-agents-in-environment --region ap-southeast-2 response.txt

This command should execute the function, and create a response.txt file with the following content:

{"agent":"James Bond","mission":"Get pendrive with agent list"}

Looks good!

3. Some rules

There are some restrictions and good-to-know-s with Lambda environment variables.

Environment variable names can only consist of alphanumeric characters and underscore, which is what you are used to when writing code anyway.

By default, when a Lambda function is created, it’s called the $LATEST version. Versioning is out of scope for this post, but it’s possible to have multiple versions of a Lambda function. If another version is created, it will need a new set of values for the environment variables, because they are restricted to a single version. This means that if $LATEST is version 2 of a particular function, then the environment variables defined in that version won’t be available in version 1, and they will should be defined there separately.

When the Lambda function is created and deployed, the environment variable is automatically encrypted using the default KMS key, which is generated in each region upon account creation. When the function is invoked, the environment variables get decrypted, and the good news is that we don’t have to do anything, Lambda does it all for us.

But if one wants to store sensitive information, and it’s important that the environment variables be encrypted during deployment, a custom key needs to be generated and used. This will probably be the topic of another post.

Anyway, other AWS services such as Parameter Store or Secrets Manager are probably better options for encrypting sensitive information.

In both cases, Lambda needs extra permission to encrypt and decrypt the secrets.

4. Summary

Environment variables behave in a similar way in Lambda functions as they do in a normal NodeJS code.

They are automatically encrypted by KMS and get decrypted when the value is needed during function invocation. Lambda manages the process as well as the permissions, so the account owner doesn’t have to do any of these.

Thanks for reading, and see you next time.