An experimental (and opinionated, and probably over-engineered) CLI utility to manage secrets as part of code, managed with your source control system. This utility is heavily inspired by ejson, and so, it strives to keep all of its benefits, which are:
- Secrets can be safely stored in a git repo.
- Changes to secrets are auditable on a line-by-line basis with git blame.
- Anyone with git commit access has access to write new secrets.
- Decryption access can easily be locked down to production servers only.
- Secrets change synchronously with application source (as opposed to secrets provisioned by Configuration Management).
There are also a couple more (opinionated) improvements:
- Keyrings are a single JSON file, instead of files named with random hashes. Now there's a single file with an explicit name to be added to
.gitignore. - By default keyrings are looked up in the current directory, so no need to modify your own system using
sudoon/opt/or whatever. - Full support for
stdin/stdout, plus explicit CLI flags to specify the input and output files. - Handy subcommand to add and encrypt secrets to an existing secrets file; less hand editing files.
- Public keys are embedded in the encrypted secrets themselves, instead of a single public key shared for all the secrets in the document. This allows for two things:
- JSON documents can now have an arbitrary structure, now there's no requirement for them to be an object. We could have a JSON document containing an array of strings, the individual strings inside the array can be encrypted too.
- We can construct an encrypted secrets file in which each secret can have independent public keys, so, when decoding, only the keys in the keyring with a corresponding secret key will be decrypted; the rest of the secrets (those that couldn't be decrypted) will be left as-is in the output.
Unfortunately, at this moment, Privie can not be recommended for production environments, mainly because:
- I'm not cryptography expert! (Help is welcomed)
- The cryptographic libraries used provide a good API, however they haven't been (yet) properly audited.
A keyring is a JSON document with a mapping of public keys and (optionally) their corresponding secret keys. We only need a public key to encrypt a secret, but we do need its corresponding secret key to decrypt that encrypted secret.
A keyring can be generated like this:
$ privie generate-keyring > my-keyring.json
# or
$ privie generate-keyring --output=my-keyring.json
By default generated keyrings are dumped to stdout, so we can use shell redirections (>) or the explicit --output parameter.
Now we have a keyring file that looks like this:
cat my-keyring.json
{"17EV2Rohy...Th0tJF36qfAw=":"hy9ZzHBn...8dpiqdIBxck8="}
Keyrings contain the secret keys required for decryption, so it's very important we keep these safe. DO NOT commit these to any code repository!.
Assuming we have a file called my-secrets.json with this content:
{
"mongodb": "mongodb://m-user:m-pass@some-server.mlab.com:23022/someDb",
"redis": "redis://r-user:r-pass@some-server.cloud.redislab.com:12345/1"
}We can encrypt it using the keyring from the previous example:
$ privie encrypt --keyring=my-keyring.json --input=my-secrets.json --output=my-encrypted-secrets.json
We can also use `stdin` and/or `stdout` instead of `--input` and/or `--ouput`
For example, this command is equivalent to the previous one:
$ privie encrypt --keyring=my-keyring.json < my-secrets.json > my-encrypted-secrets.json
Now we have a my-encrypted-secrets.json file that looks something like this:
$ cat my-encrypted-secrets.json
{
"mongodb": "17EV2Rohy...Th0tJF36qfAw=:qeGq09vNaBoDda8RWnmnT2zxF/B5jIeEtQ894Uy4...g9zBE2BPa08DVzt1sjHJxe",
"redis": "17EV2Rohy...Th0tJF36qfAw=:cDbrdgVu446IA475Hq8HTDJb7FMKGXtlG8YAgGCbr1...7Cq0FXl+O41kKRSfEix3f70pJDK1K"
}
Now our secrets are encrypted and can be committed to our repository:
$ git add my-encrypted-secrets.json
$ git commit -m 'Add encrypted secrets file'
$ privie decrypt --keyring=my-keyring.json --input=my-encrypted-secrets.json --output=my-decrypted-secrets.json
Similar to when decrypting; we use the decrypt subcommand instead of encrypt and reverse the --input and --output params. Now we have a my-decrypted-secrets.json file which is "equivalent" to the initial my-secrets.json file we had at the beginning. (They won't be exactly equal, since most formatting will be lost every time a JSON document is read and written).
This is where is up to the team to decide where to keep this file, and also, how to make it available in CI. For example, its possible to paste the content of the keyring as a Github secret, and write it to a file just before invoking privie:
- name: Decrypt the secrets file
env:
KEYRING_CONTENTS: ${{ secrets.KEYRING_CONTENTS }}
run: |
echo $KEYRING_CONTENTS > my-keyring.json
privie decrypt --keyring=my-keyring.json --input=my-encrypted-secrets.json --output=my-decrypted-secrets.jsonYou can also have the keyring's content piped in via stdin, like so:
$ echo $KEYRING_CONTENTS | privie decrypt --keyring=- --input=my-encrypted-secrets.json --output=my-decrypted-secrets.json
Most commands with a --keyring argument will allow using - to denote to read from stdin, the limitation being that it won't be possible then to use stdin for the --input argument.
// TODO: Add more examples