GnuPG, pass and environment variables

nix linux wsl

Vault is a popular secret store in the production thanks to its helm integration: the secrets are injected to the pods via helm side car. No secrets are exposed in the plaintext in the deployment pipeline. 🎉

I would like to adopt the similar approach to manage secrets in the local development such as the database credentials, API keys, etc. They are currently all over the place:

Pass and GnuPG

First, we need a secure secret store, such as pass. It uses the GnuPG to store

the secrets, we can retrieve the secrets as long as we can pass the gpg-agent’s challenge.

You may install and configure the GnuPG and pass with this PR if you use nix. It configures the pinentry to use curses interface and disable the SSH support for the time being.

Invoke gpg --gen-key to generate the the private / public key pair. Then we can initialize the pass store by following this guide:

pass init "<The name of GPG key>
pass git init

As I am only use pass for command line secret management, I use the corporation/ENV_KEY naming convention, here is my pass store;

❯ pass
Password Store
└── epicgames
    └── CODEFRESH_API_KEY

Then I can replace the secret in plaintext with the following snippet in the .envrc:

export CODEFRESH_API_KEY=$(pass epicgames/CODEFRESH_API_KEY)

The first time, you will be prompted with the pinentry for the pass phase of the private key, then it just works as long as the gpg session is not expired.

Future work

PGP, and its open source counterpart, GnuPG is complicated for this simple use case. Once I am in the gpg bandwagon, I may explore other usages:

Caveats under Windows

After I published this post, I suddenly realized the above solution does not solve my problem, at all — how could I populate the environment variables for IntelliJ in build and run time without exposing the secrets in plaintext?

There are several options, but none of them just works ™.

JetBrains Gateway

The JetBrains Gateway is still in beta, but it is probably the most promising approach. It worked like VsCode remote development: the server runs in the remote machine, WSL more concretely, then the UI client connects to the server for rendering. Unfortunately, the server is a puppet of the client. It does not support direnv, and I have no idea how to inject the environment variables from the shell.

JetBrains Gateway in Action
JetBrains Gateway in Action

I really have high hope on this project, as it seems to lead to the right direction to do remote development right. /sigh.

Remote Debugging

We can run maven or gradle in WSL to build, test the project in the shell configured by the direnv, and just use IntelliJ for editing and debugging. This might be a reasonable compromise, and it should work. But I just miss the one-click convenience to run gradle tasks.

All-in Windows

We can try to replicate the above setup in Windows:

This might work, but I loathe to setup another shell environment with inconsistent behaviors while I already have one. I might just stick to cygwin if I could settle for a suboptimal experience, — personal opinion, no offense.

All-in Linux

This is the exactly opposite of the above direction, we can run IntelliJ as a WSL GUI application, recommended in this guide.

I still consider the WSL is an augment for Windows developer to access Unix environment. If I decide to go this far, maybe I should just pull the trigger to ditch Windows and install NixOS on my workstation.

Closing Thoughts

Every time I am frustrated with the tug of war of Windows application and shell, I just eyed my MacBook Pro: there is a reason developers pay premium for a coherent development environment.

Footnotes

  1. If you use zsh, you can set HIST_IGNORE_SPACE option to avoid persisting sensitive data in the history.