Skip to content

Commit

Permalink
Add README
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewdowney committed Jun 23, 2021
1 parent b17275a commit a935a3b
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 14 deletions.
171 changes: 171 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# secrets

For storing application secrets on disk with client-side encryption.

Uses [pbkdf2](https://en.wikipedia.org/wiki/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](https://github.com/edn-format/edn) map, with arbitrary levels of
labeled nesting.

By default, the secrets file lives at .secrets.edn in the working directory,
but this path can be changed explicitly via `with-path` (or via the `:path`
flag at the command line).


## Contents
- [Usage from the comand line](#usage-from-the-command-line)
- [Usage from Clojure](#usage-from-clojure)

## Usage from the command line

### Install

- Download the latest release.
- Make it executable.
- Move it somewhere on your `$PATH`.

One-liner:
```
$ TODO
```

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>`.


### Editing secrets

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=="}
```

### Reading & writing secrets

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)
```


### Passing secrets to programs

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.

```clojure
;; prod.env
{"API_KEY" "[:bitso :prod :key]"
"API_SECRET" "[:bitso :prod :secret]"}
```

```python
# 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.


## Usage from Clojure

```clojure
(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
```
15 changes: 14 additions & 1 deletion src/io/sixtant/secrets.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
(ns io.sixtant.secrets
"For storing application secrets on disk with client-side encryption."
"For storing application secrets on disk with client-side encryption.
Uses [pbkdf2](https://en.wikipedia.org/wiki/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](https://github.com/edn-format/edn) map, with arbitrary levels of
labeled nesting.
By default, the secrets file lives at .secrets.edn in the working directory,
but this path can be changed explicitly via `with-path` (or via the `:path`
flag at the command line)."
(:require [buddy.core.codecs :as codecs]
[buddy.core.nonce :as nonce]
[buddy.core.crypto :as crypto]
Expand Down Expand Up @@ -215,6 +227,7 @@
[f & args]
(-> (read-secrets)
(update :data #(apply f % args))
(update :password #(or % (read-password "Password:")))
(write-secrets)))


Expand Down
25 changes: 12 additions & 13 deletions src/io/sixtant/secrets/main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@


(defcommand inspect
"Inspect the structure of the stored hierarchy for secrets, replacing any
secret values with `***`."
"Inspect stored secrets in cleartext, with values replaced with '***'."
["inspect"
"inspect :path my-secrets.edn"]
[_ & args]
Expand All @@ -56,8 +55,7 @@


(defcommand write
"Write the (EDN compliant) secret to the given path with assoc-in logic.
(See also: read)"
"Write the (EDN compliant) secret at some path (see also: read)."
["write \"[:bitso :prod]\" '{:key \"abc\" :secret \"def\"}'"
"write \"[:bitso :prod :key]\" \"foo\" :path different-file.edn"]
[_ path secret & args]
Expand All @@ -69,8 +67,7 @@


(defcommand read
"Attempt to read the secret at the given path with get-in logic.
(See also: write)."
"Read the secret at the given path (see also: write)."
["read \"[:bitso :prod]\""
"read \"[:bitso :prod :key]\""]
[_ path & args]
Expand All @@ -82,8 +79,7 @@


(defcommand swap
"Eval the function string and apply it to the secrets map, writing the
new result."
"Eval the clojure function string and use it to update the stored secrets."
["swap '#(assoc-in % [:bitso :prod :client-id] 123)'"
"swap '(fn [x] (update-in x [:bitso :prod] merge {:client-id 123}))'"]
[_ f & args]
Expand All @@ -105,8 +101,10 @@


(defcommand with-env
"Take a map of string -> secret-path-vector, resolve each secret, and
merge the resulting map of string -> secret into the environment variables
"Temporarily put secrets in environmental variables and execute a command.
Uses a map of string -> secret-path-vector, resolves each secret, and
merges the resulting map of string -> secret into the environment variables
before executing the command."
["with-env '{\"BITSO_KEY\" \"[:bitso :prod :key]\", \"BITSO_SECRET\" \"[:bitso :prod :secret]\"}' my-script.py"]
[& args]
Expand Down Expand Up @@ -167,10 +165,11 @@
(assoc x :data (read-string out))))



(defcommand edit
"Edit the encrypted API keys with the default text editor. This uses `vipe`,
so it will only work on *nix systems with moreutils installed."
"Edit the encrypted API keys with the default text editor.
This uses `vipe`, so it will only work on *nix systems with moreutils
installed."
["edit"
"edit :path some-other-keyfile.edn"]
[_ & args]
Expand Down

0 comments on commit a935a3b

Please sign in to comment.