For storing application secrets on disk with client-side encryption.
Uses pbkdf2 with sha512 (100,000 iterations) to convert a passphrase into a key, and encrypts the secret data using AES256 CBC + HMAC SHA512.
Any one secrets file contains an encrypted version of a single EDN map, with arbitrary levels of labeled nesting.
The path used for the secrets file, in priority order, is one of:
- the one explicitly specified via
with-path
(or:path
at the command line), - the
.secrets.edn
file in the working directory, or - the
.secrets.edn
file in the home directory.
- Download the latest release.
- Make it executable.
- Move it somewhere on your
$PATH
.
wget https://github.com/sixtantIO/secrets/releases/latest/download/secrets
chmod +x secrets
mv secrets /usr/local/bin
Alternatively, clone this repository, build the jar (clojure -X:uberjar
),
embed the jar in an executable shell script
(cat stub.sh secrets.jar > secrets && chmod +x secrets
), and move it onto
your path (mv secrets /usr/local/bin/
).
Otherwise, invoke the jar directly with java -jar secrets.jar <options>
instead of using secrets <options>
.
On linux systems with vipe
installed (from the moreutils
package), you
can edit an encrypted file using the default system editor:
$ secrets edit :path some-secrets.edn
After creating a passphrase, the editor will pop up. Write some valid EDN data:
{:some-service-name {:prod {:key "abc" :secret "123"}}}
Now when you save & close, the data is encrypted to disk:
$ secrets edit :path some-secrets.edn
Set password:
Confirm password:
Encrypting data for writing... Done.
Wrote 171 bytes.
$ cat some-secrets.edn
{:data "MeDQAmkXCx2kGPEJbSaL8l82wbiGpBmvFn1FgZEk8ysDoGe/A6edqQ3+0GWS+MAOxAxraaTPjdXid12sGqeITv1yQuvtzS79swoTFOGwLCYmcQHjJB6FC9zkwKbY3LjA", :iv "Yh/SVZShqynxcV7koItBWw=="}
Write a key/secret pair for Bitso, labeled :prod
, using the default secrets
file, and then read it back:
$ secrets write "[:bitso :prod]" '{:key "abc" :secret "def"}'
Password:
Encrypting data for writing... Done.
Wrote 151 bytes.
$ secrets read "[:bitso :prod]"
Password:
{:key "abc", :secret "def"}
The hierarchy of the secrets file now looks like this:
$ secrets inspect
Password:
{:bitso {:prod {:key "***", :secret "***"}}}
Add another secret, maybe a personal key, and watch the hierarchy change:
$ secrets write "[:bitso :personal]" '{:key "foo"}'
Password:
Encrypting data for writing... Done.
Wrote 195 bytes.
$ secrets inspect
Password:
{:bitso {:prod {:key "***", :secret "***"}, :personal {:key "***"}}}
You can also evaluate clojure functions against the secrets map, e.g. to list
all of the keys nested under :bitso
:
$ secrets eval "#(keys (:bitso %))"
Password:
(:prod :personal)
Use with-env
to retrieve stored secrets and expose them as environment
variables to the process launched by some command.
Quick and dirty example:
$ secrets with-env '{"KEY" "[:bitso :prod :key]"}' python3 -c "import os; print(os.environ['KEY'])"
Password:
abc
You can specify different mappings in local files, e.g.
;; prod.env
{"API_KEY" "[:bitso :prod :key]"
"API_SECRET" "[:bitso :prod :secret]"}
# app.py
import os
print(os.environ["API_KEY"])
print(os.environ["API_SECRET"])
$ secrets with-env "$(cat prod.env)" python3 app.py
Password:
abc
def
This way, your configuration files are specifying which keys to use without revealing what they are, and the contents never have to sit on disk in cleartext.
(in-ns 'io.sixtant.secrets)
; Use the `with-secrets` macro around any code which needs access
; (prompts for a password)
(with-secrets
(println "Have secrets for:" (keys (secrets)))
; `with-secrets` is reentrant, so this second invocation doesn't prompt
; for anything
(with-secrets
(println "Bitso prod key:" (secrets :bitso :prod :key))))
; Have secrets for: (:bitso)
; Bitso prod key: abc