~egtann/shh

secret management
2f8159f5 — Evan Tann 28 days ago
remove pledge, unveil for edit for arbitrary editors
74031853 — Evan Tann 28 days ago
correct add-user example
3d5d5192 — Evan Tann 28 days ago
bump version to v1.8.1

clone

read-only
https://git.sr.ht/~egtann/shh
read/write
git@git.sr.ht:~egtann/shh

You can also use your local clone with git send-email.

shh

shh manages secrets for projects and small teams. Secrets are encrypted and safe to commit to version control software like git.

Unlike Hashicorp Vault, shh requires no infrastructure. There's no server to manage and secure -- just a single file.

NOTE: This code has not been audited, and was not written by an expert cryptographer. Caveat emptor.

While I make every effort to follow Go stdlib's crypto examples closely and research best practices, I am not an expert in the field. There might be vulnerabilities, and some might be severe. Until this is audited, you should treat secrets stored by shh as better than plaintext, but not by much.

If you have a background in cryptography and wish to help audit or provide feedback, please reach out on the mailing list.

Install

$ go get -u egt.run/shh

Getting started

First, generate encryption keys:

shh gen-keys

You'll be asked to choose a username and set a password. Be sure to remember your password.

gen-keys places keys in ~/.config/shh. Back up your id_rsa private key; if lost there's no way to recover it!

Next, initialize a new project:

shh init

This creates a .shh file in your current working directory which will be used to store your secrets. Your public key was automatically added to the .shh file, so you can now add secrets, like so:

shh set staging/env "$(cat staging.env)"

Where staging/env is the name of the secret and the content of the file staging.env is the secret itself.

You can retrieve that secret with get like this:

shh get staging/env

You'll have to enter your password to retrieve the secret.

NOTE: There's no concept in shh of directories or /, but it's useful to namespace your secrets for glob matches as described later.

Also note that secrets belong to a global namespace. If Bob creates secret A, but Alice doesn't have access, Alice trying to create a secret named A will result in an error. The same name resolves to the same secret for every user in the project, regardless of whether or not that user has access.

If you need to edit your secret, shh edit staging/env can do it. That uses your $EDITOR of choice. Note that $EDITOR should be an absolute path. Save and quit to re-encrypt the updated version without ever saving an unencrypted version to disk.

You can rename a secret with rename like this:

shh rename old-name new-name

If you want to start a new secret using another secret as a starting point and maintain all teammates' access:

shh copy old-name new-name

This may be useful when you're setting up a new environment, such as:

shh copy production/env staging/env

Team management

You can grant and revoke access to secrets among teammates at any time. First ensure they're added to the project, which will require their public key generated via gen-key:

shh add-user alice@example.com "$(cat alice_id_rsa.pub)"

Now they're added to the project, but they don't have access to any keys:

shh allow alice@example.com staging/env

You can only allow access to keys which you, yourself, have access to.

Save time by using glob matches like this to grant access to an entire namespace:

shh allow alice@example.com staging/*

You can revoke access to individual or globbed keys, like this:

shh deny alice@example.com staging/env

Or you can remove a user from a project entirely, which will remove all of their secrets and delete their public key from .shh:

shh rm-user alice@example.com

Project-level settings

If you want to change your username on a per-project basis, create a .shhconfig file next to .shh at the project's root. The format is the same as ~/.config/shh/config:

username=bob@example.com

Advanced usage

Serve and login

If you're using shh, you'll probably need to retrieve secrets during deploys and other scripts. That's why there's shh serve, which saves your password in memory for 1 hour.

You can set the port in your ~/.config/shh/config file like this:

username=bob@example.com
port=4850

Then run shh serve and from another terminal run shh login to set your password in memory. Now you can run get or allow without needing to enter your password each time -- especially useful during deploy scripts.

Rotate

If your private key is compromised or you need to change your password, you can easily change your keys:

shh rotate

This will ask for a new password, generate new keys and re-encrypt all secrets using that new password.

Using the command line

See the difference in secrets granted between two users:

diff -y <(shh show alice@example.com) <(shh show bob@example.com)

Edit all files containing a regular expression:

shh search "\d{8,}" | xargs -I % -o shh edit %

Count the number of secrets to which a user has access:

shh show bob@example.com | wc -l

See the changes in an shh file across commits:

vimdiff \
    <(shh -f <(git show commit_1:.shh) get my_secret) \
    <(shh -f <(git show commit_2:.shh) get my_secret)

Key commands

shh init                        # initialize project, creating .shh file
shh gen-keys                    # generate keys
shh get $secret_name            # get secret or secrets
shh set $secret_name $value     # set value
shh del $secret_name            # delete secret
shh allow $user $secret         # allow access to secret
shh deny $user $secret          # deny access to secret
shh add-user [$user $pubkey]    # add user to project, default self
shh rm-user $user               # remove user from project
shh show [$user]                # show user's allowed and denied keys
shh search $regex               # list all secrets containing the regex
shh edit                        # edit secret using $EDITOR
shh rotate                      # rotate your key
shh serve                       # start server to maintain password in memory
shh login                       # login to server
shh version                     # version info
shh help                        # usage info

Example usage:

# Create secret file and keys.
shh init
> creating new .shh
>
> username (usually email): alice@example.com
> password:
> confirm password:
>
> generated ~/.config/shh/config
> generated ~/.config/shh/id_rsa
> generated ~/.config/shh/id_rsa.pub
> created .shh
>
> be sure to back up ~/.config/shh/id_rsa and remember your password, or you
> may lose access to your secrets!

# Add user to an existing project (on a different computer than above)
shh init
> adding user to existing .shh
>
> your username (usually email): bob@example.com
> password:
> confirm password:
>
> generated ~/.config/shh/config
> generated ~/.config/shh/id_rsa
> generated ~/.config/shh/id_rsa.pub
> added bob@example.com to .ssh
>
> be sure to back up ~/.config/shh/id_rsa and remember your password, or you
> may lose access to your secrets!

# Create a secret named "database_url"
shh set database_url $DATABASE_URL

# An alternative syntax to set a secret from a file
shh set very_secret "$(< secret.txt)"

# You can also namespace the secrets like a filesystem. There's no built-in
# support for this, but it makes it easy to support different projects/repos
# within a single project.
shh set my_project/staging/database_url "127.0.0.1:3304"

# Allow a user to access a secret
shh allow bob@example.com database_url

# Deny a user from accessing a secret
shh deny bob@example.com database_url

# Deny a user from accessing all secrets. The quotes are necessary
shh deny bob@example.com "*"

# Deny a user from accessing any secrets matching a glob pattern
shh deny bob@example.com staging/*

# Show your accessible keys and meta info
shh show

# Show Bob's keys and meta info
shh show user bob@example.com

# Show all user keys and meta info
shh show user "*"

# Show all secrets containing the regular expression
shh search "example.(com|net)"

# In case of stolen key, you can regenerate/rotate your key
shh rotate
> old password:
> new password:
> confirm password:
>
> generated ~/.config/shh/id_rsa
> generated ~/.config/shh/id_rsa.pub
>
> be sure to back up ~/.config/shh/id_rsa and remember your password, or you
> may lose access to your secrets!

# Stream staging secrets to server.env on deploy
shh get staging/env | ssh alice@staging "cat > server.env"

Encryption details

shh uses envelope encryption to keep your project secrets secure. gen-key creates 4096-bit RSA keys in your home directory, encrypting the private key with a mandated 24-char minimum length password, which is long enough to prevent re-use/memorization and forcing use of a password manager.

Each secret is encrypted with a random AES-256 key using GCM. The AES key is encrypted using your RSA public key and stored alongside the secret.

Security bulletins

v1.8.0

As of v1.8.0, the following security vulnerability is fixed:

  • Previously, secrets were vulnerable to a padding oracle attack, as our use of AES-CFB did not include any authentication mechanism. Switching to AES-GCM prevents this attack. You should regenerate any keys that were stored in .shh and shared.

Project Contact

The best way to communicate bugs or provide patches is through the mailing list.

Future improvements

  • Add tests
  • v2: Use ssh-agent rather than homegrown server. Removes shh serve and shh login, -n non-interactive mode, memguard dependency