Just in time secrets access!
Why?
We have to talk about secret security for the local environment. I've seen many times when a .env
file with personal AWS Access Keys was committed and pushed to the git. Why this happens in most cases?
- application requires access to cloud resources (like SQS queues)
- there is a missing entry in
.gitignore
a file to not include.env
the file (or any*.env
files)
One could say that adding this file to .gitignore
is enough, and the problem is fixed, but I won't agree - new repository, you forget to update .gitignore
and you have the same issue again. The real problem is that secrets are stored in plain text anywhere in your filesystem.
So let's settle that:
- secrets shouldn't be stored in any unencrypted files
- secrets shouldn't be used in any CLI programs as arguments (to not store them in a shell history)
What?
Let's fix that. We will store secrets securely - encrypted and with the required authentication mechanism.
Secrets will be injected into environment variables only when needed:
- When entering a catalog (i.e.: application cloned repository)
- With automatic cleaning if the directory is left!
- When an application is run (for example
terraform plan
)
How
π Direnv
First, let's evaluate this magnificent tool - direnv
.
Install it according to your operating system and hook it into your shell.
Now, whenever you enter a directory where .envrc
file is defined (or any subdirectory), you will see the following warning:
β― cd direnv-demo
direnv: error /Users/krzysztof.wiatrzyk/blog/direnv-demo/.envrc is blocked. Run `direnv allow` to approve its content
If you run direnv allow
then direnv
will execute that .envrc
file - it can include whatever you must do when entering a directory.
Example:
β― ls
direnv-demo
β― echo $DIRENV_DEMO
β― cat direnv-demo/.envrc
export DIRENV_DEMO="true"
β― cd direnv-demo
direnv: error /Users/krzysztof.wiatrzyk/blog/direnv-demo/.envrc is blocked. Run `direnv allow` to approve its content
β― direnv allow
direnv: loading ~/blog/direnv-demo/.envrc
β― echo $DIRENV_DEMO
true
β― cd ..
direnv: unloading
β― echo $DIRENV_DEMO
β― cd direnv-demo
direnv: loading ~/blog/direnv-demo/.envrc
β― echo $DIRENV_DEMO
true
direnv
executes .envrc
file similar to what source .envrc
would do. This means that content of this file is a shell script and not direct environment variables like .env
file used by docker compose. TLDR:
β -
export SECRET=123
β -
SECRET=123
Let's go to another tool...
π Gopass
gopass
is a newer (and in Go!) implementation of pass
- CLI password manager.
It allows to securely (in encrypted form) store all required secrets. By default, it uses PGP to encrypt secrets (age
also can be used). From additional features, automatic backups to the git repository are available, but it's out of the scope of this article.
Download and set it up using gopass setup
, use a PGP key with a password.
Now, if all is set up, you can store, list and view secrets:
β― gopass insert demo/secret
π₯ Syncing with all remotes ...
[<root>]
fs pull and push ... Skipped (no Git repo) (no changes)
done
β
All done
Enter password for demo/secret:
Retype password for demo/secret:
# Password is not displayed in the console!
β― gopass search demo/
demo/secret
β― gopass demo/secret
# Here I'm asked to provide a passphrase for PGP
Secret: demo/secret
123

π A marriage of Gopass + Direnv
So direnv
and gopass
can be used together - you will get secrets automatically injected into shell when you enter a directory and they will get unloaded when you leave it.
# Edit `.envrc` file and add additional export
# export SECRET=$(gopass demo/secret)
β― nano .envrc
direnv: error /Users/krzysztof.wiatrzyk/blog/direnv-demo/.envrc is blocked. Run `direnv allow` to approve its content
# Approve changes in `.envrc` file
β― direnv allow
direnv: export +SECRET
# Check that secret is set
β― env | grep -i secret
SECRET=123
And this trick doesn't work only with gopass
, it will work with other secret providers as well.
AWS SSM Parameter Store
#!/bin/bash
export AWS_PROFILE=development
export AWS_REGION=eu-central-1
export SECRET=$(aws ssm get-parameter --name "/my-secret" --with-decryption | jq -r ".Parameter.Value")
.envrc
Doppler (SaaS Secrets Platform)
#!/bin/bash
doppler setup -p terraform -c development
set -a
source <(doppler secrets download --no-file --format env)
set +a
.envrc
Vault
#!/bin/bash
export VAULT_ADDR=https://vault.windkube.com
# GoPass is used to securely store token for Vault API
export VAULT_TOKEN=$(gopass vault-token)
export SECRET=$(vault kv get --field SECRET kv/development/my-app)
.envrc
Infisical
(Open Source Doppler)
#!/bin/bash
# Infisical project configuration file should exist in that directory
export $(infisical export | tr -d "'")
.envrc
{
"workspaceId": "63ee5410a45f7a1ed39ba118",
"defaultEnvironment": "test",
"gitBranchToEnvironmentMapping": null
}
.infisical.json
But wait a second, this doesn't cover a case where only application process that is run locally should have this secrets exposed, isn't it?
Taskfile + GoPass
Taskfile (a YAML-based Makefile) can set environment variables based on a shell command. This allows you to prefetch a secret before running the application and put it only for the application process.
version: 3
tasks:
terraform:plan:
env:
CLOUDFLARE_API_KEY:
sh: gopass cloudflare/api-key
cmds:
- terraform plan
Taskfile.yaml
This way, you don't have this environment variable in your shell, so you won't use it by mistake!
Extras
Kubernetes in Gopass
My colleague (thanks, Leszek!) sent me the following article that shows how to use pass
to secure access to kubeconfig
files. I won't reinvent the wheel here in this article; just read Sheogorath's blog! ;)
