Just in time secrets access!

Feb 14, 2025



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:

  1. When entering a catalog (i.e.: application cloned repository)
    1. With automatic cleaning if the directory is left!
  2. When an application is run (for example terraform plan)

How

πŸ“ Direnv

First, let's evaluate this magnificent tool - direnv.

GitHub - direnv/direnv: unclutter your .profile
unclutter your .profile. Contribute to direnv/direnv development by creating an account on GitHub.

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.

GitHub - gopasspw/gopass: The slightly more awesome standard unix password manager for teams
The slightly more awesome standard unix password manager for teams - gopasspw/gopass

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!

πŸ’‘
This method will work with any secrets provider mentioned above (AWS SSM, Vault, Infisical, etc...)

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! ;)

Store Kubernetes Credentials in pass
Kubernetes is a powerful orchestration software that puts a lot of power into its CLI interface kubectl. However when it comes to credential storage, it’s rather mediocre. While it provides all you need, by default, it’ll store your access tokens, certificates or alike in your home directory in plaintext. This article should explain how to migrate them into the password manager pass, which can be secure using GnuPG and e.g. a YubiKey or smart card.

Krzysztof Wiatrzyk

Big love for Kubernetes and the entire Cloud Native Computing Foundation. DevOps, biker, hiker, dog lover, guitar player, and lazy gamer.