Setting secrets as environment variables

Before going ahead, you should know that there are common security concerns surrounding storing secrets as environment variables.

The main security concerns here are:

  • Controlling and tracking which process has access to which environment variables requires explicit commands and is often overlooked. This can make environment variables available to processes should not have access to them.
  • Many processes take the whole environment and print it or include it in error reporting, exposing any secrets stored in environment variables.

You can read more in this article by Michael Reinsch.

SecretHub supports environment variables because the majority of people that follow the 12-Factor App guidelines are already consuming secrets as environment variables in production and those secrets need to be managed too. Now that we understand the pitfalls of storing secrets in environment variables, we’ll see how SecretHub explicitly limits which secrets are accessible to which processes with the secrethub run command.

Before we can pass secrets to a process as environment variables, we need to run secrethub set first. This time, we’re going to use an env type in our secrets.yml file, looking like this:

secrets:
    # env indicates we want to create an environment with secret variables defined below.
    - env:
        # name sets the name of the environment. This can be helpful when setting multiple environments for different processes. 
        # If not set, name defaults to `default`. 
        name: "example_env"
        # vars includes key-value pairs with the env var to set with the source secret.
        vars:
            # We're setting two variables here that our example_env needs.
            DB_USER: <insert_username_here>/testing/db_user:latest
            DB_PASSWORD: <insert_username_here>/testing/db_password:latest

On Linux/macOS, we can run the code below to generate the secrets.yml file for us:

# on Linux/macOS
cat << EOF > secrets.yml
secrets:
    - env:
        name: "example_env"
        vars:
            DB_USER: ${SH_USERNAME}/testing/db_user:latest
            DB_PASSWORD: ${SH_USERNAME}/testing/db_password:latest
EOF

For Windows, we can run the code below to generate the secrets.yml file for us:

# on Windows
'secrets:
    - env:
        name: "example_env"
        vars:
            DB_USER: '+$SH_USERNAME+'/testing/db_user:latest
            DB_PASSWORD: '+${SH_USERNAME}+'/testing/db_password:latest
' > secrets.yml

So, now that we have our secrets.yml file, we can run the secrethub set command again.

secrethub set

As we can see, this hasn’t actually set the environment variables yet. To verify this, we can run:

echo "DB_USER => $DB_USER \nDB_PASSWORD => $DB_PASSWORD"

When we want to load the secret environment variables and expose them to only one process, we use the secrethub run command with the example_env we have just set:

secrethub run --env example_env echo "DB_USER => $DB_USER \nDB_PASSWORD => $DB_PASSWORD"

This should print out the secrets like this:

$ secrethub run --env example_env echo "DB_USER => $DB_USER \nDB_PASSWORD => $DB_PASSWORD"
DB_USER => example_db_user
DB_PASSWORD => example_password123

Note that if we run the plain command without secrethub run, we don’t have access to the secrets either:

echo "DB_USER => $DB_USER \nDB_PASSWORD => $DB_PASSWORD"

And that’s all there is to exposing secrets as environment variables to single processes. Now we still have to clean up, so let’s run the secrethub clear command again:

secrethub clear

Next Steps

Now that we have seen how secrets can be presented on a system in many ways, it is time to start thinking how real services would use this. See our next steps section for recommendations where to start.