Storing related secrets in Parameter Store for more efficient access

We come across a situation sooner or later in our development career where we need to use multiple related secrets to achieve something. We can manage these parameters by storing and fetching them as an object in Parameter Store, which will make our code shorter and easier to read.

1. The scenario

Let’s say that we need to authenticate to an external service. We want our application to connect to MongoDB, so we’ll need to use multiple parameters to build the connection string. The application will need to know the database name, username, and password.

Storing them one by one in a different secret means we need to get them separately either at build time (and persist them as environment variables) or run time (the application fetches them directly).

Say we don’t want to store the secret as an environment variable in a Lambda function’s configuration, and we can get it from a secure store like Parameter Store at run time.

2. The problem

2.1. Adding parameters takes more time

A natural solution would be to store the multiple parameters in separate secrets. For example, we can add the database name with the following command:

aws ssm put-parameter --name /dev/db-name --value my-db-name --type SecureString

Here the name property refers to a path parameter, which is a good practice because it has several advantages.

We have to repeat the above process a couple of times to create the rest of the secrets. We also need to store a username and password, so we’ll have to make two more API calls.

2.2. Multiple API calls to get the parameters

Just like adding the secrets to Parameter Store, we need to fetch them from there if we want our application to use them.

It means multiple API calls to Parameter Store. If one of them fails for a reason, the application won’t be able to connect to the database.

3. A solution

Instead of storing the values in separate parameters, we can create a stringified object which has the secrets as key-value pairs:

aws ssm put-parameter --name /dev/db-config --value \
'{"dbName": "my-db-name", "dbUsername": "my-db-username", "dbPassword": "my-db-password"}' \
--type SecureString

This way, we only have one parameter that contains all the information we need!

The only caveat is that we have to ensure that the size of the object won’t exceed the 4kB parameter size limit.

After saving the secret object, we’ll only need one function call to get the whole set of parameters.

We can write something like this to get the secrets from the store:

import AWS from 'aws-sdk';

const ssm = new AWS.SSM({
  apiVersion: '2014-11-06',
  region: 'us-east-1'
});

async function createDbConnection(environment) {
  const params = {
    Name: `/${environment}/db-config`,
    WithDecryption: true
  };

  const response = await ssm.getParameter(params).promise();
  const { dbName, dbUsername, dbPassword } = JSON.parse(response.Parameter.Value)

  // use dbName, dbUsername and dbPassword to build the connection string
  const connectionString = `mongodb+srv://${dbUsername}:${dbPassword}@cluster0.example.com/${dbName}?authSource=admin`

  return connectionString
}

We used one API call to Parameter Store and got the whole object. After parsing the stringified object, we can access the individual as keys and values.

We can now easily create the database connection string and return it.

4. Summary

We can store related secrets or authentication parameters as stringified objects in Parameter Store. It will reduce code complexity and the number of calls at both up-and-download times. Writing shorter and cleaner code to achieve the same result is always a win!

5. References and further reading

For more information about path parameters and how they can help organize secrets, visit AWS documentation page on the topic.