Envisionment : donjon, a simple, standards-based, and secure credentials store for distributed applications
donjon is a secure, low-throughput key-value store, built on top of Git (distribution), OpenSSL (encryption) and OpenSSH (authentication and authorisation).
The original use case is distributing secrets for 3rd-party applications (think AWS S3) to web applications without storing them in the codebase, or anywhere in plain text.
Once donjon is set up, running a service with an environment containing your credentials is a one-liner (example of a Rack server):
env `donjon` bundle exec rackup
donjon is written in Ruby annd distributed as a Rubygem, but is entirely framework.
Issues addressed by donjon
The way we manage credentials (S3 keys, MySQL passwords, SSL private keys, etc) tends to be insecure and hard to maintain.
- Codebases are littered with clear-text credentials in configuration files;
- Credentials are repeated in multiple projects;
- Some credentials (SSL certificates for instance) are distributed by a central server (puppet, chef); for “security” these credentials and the corresponding codebase are not versioned;
- Because of the above, it is difficult to update or distribute credentials across a complex system (multiple services, machines, codebases);
- We have no way to prevent credentials from being used by a compromised machine/account.
Base concepts & guidelines
A credential store is just a key/value store with a few specifics:
- it get read often, by multiple clients services;
- it rarely gets written to, and then only by humans;
- it needs to be security-conscious—authorise only specific actors to read or write.
Several if the underlying technical challenges have been solved elsewhere; we’re not trying to reinvent the following wheels:
- authentication and authorisation: donjon uses SSH and/or the system (i.e. filesystem permissions);
- privacy: donjon uses OpenSSL public key encryption to encrypt credentials;
- version control and distribution. donjon uses Git to store and distribute (encrypted) credentials.
Installation & Usage
donjon installs as a Ruby gem. Assuming a fairly standard Ruby installation, just run:
$ gem install donjon
Invoke the provided
donjon init command once to perform setup:
$ donjon init It appears donjon is not configured yet on this account. Please provide the url of your donjon Git repository: > email@example.com:example.com/donjon Cloning into '/tmp/donjon.rRBngZ3m'... Configuring... I've detected only one SSH identity ~/.ssh/id_dsa, so we'll use that one. Self-testing... Done.
donjon is then just a command that sets and gets credentials:
$ donjon set AWS_ACCESS_KEY_ID AKIAJYQR456ZDS7I12AB FOO bar_baz Encrypting key/value pairs... Committing... Pushing... Self-testing... Done.
get to return a credential:
$ donjon get FOO bar_baz
Or on its own to return all credentials:
$ donjon AWS_ACCESS_KEY_ID=AKIAJYQR456ZDS7I12AB FOO=bar_baz
Use it to run commands with an environment that include credentials:
$ env $(donjon) bundle exec script/server thin
Use the gem to securely obtain credentials on-demand from application code:
> require 'donjon' > donjon.AWS_ACCESS_KEY_ID #=> AKIAJYQR456ZDS7I12AB
donjon step-by-step setup
Setting up the donjon repository
Just create your repository at Github (or elsewhere), then run
By default this will clone and use the repository to
Test it by adding a first key.
Adding and removing developers
Ask the new developer for his or her SSH identity (public key, usually in
~/.ssh/id_dsa). If necessary they can generate one with
new-dev$ ssh-keygen -t dsa
We strongly recommend protecting your SSH keys with a passphrase.
On your machine (or any account already authenticated with donjon), simply
$ donjon peer add Please give me the public key of the writer you'd like to authorise: > ssh-rsa AAAAB3NzaC1yc2EcXOSI...OuqV3 firstname.lastname@example.org Please name this user [alice]: Encrypting all credential with your private key and alice's public key... Adding alice's public key to the repository... Committing... Pushing... email@example.com is now authorized
Removing someone is just as simple:
$ donjon peer remove alice Are you sure you want to remove alice's authorisation? [y/N] y Removing credentials encrypted for alice... Removing alice's public key... Committing... Pushing... firstname.lastname@example.org is no longer authorised
Remember to also de-authorise them from your organisation’s Github account.
Setting up the donjon server
A donjon server is just another peer, simply one that can’t push to your shared repository.
- Set up a
castle.example.com, for instance.
- Create an SSH key for it.
- Allow it from your local clone, with
donjon peer add, as above.
- Allow it to pull from your Github account (but not to push).
donjon initon the account.
Remember to set up a
cron(8) job to update the credentials store regularly:
$ crontab -e # update credentials every 10 minutes */10 * * * * donjon update
Setting up a donjon client
donjon leverages (and trusts) SSH for authentication and authorisation.
If a client doesn’t have a repository, it will try to call donjon over SSH on another machine.
donjon get my_gey from a shell (or
donjon.my_key in Ruby) behaves
mostly like calling
$ ssh donjon.example.com donjon get my_key
The default server name is
donjon, in the machine’s domain name as provided by
You can control the host donjon will connect to by setting
Remember to setup your server as above, and to allow your client to connect via
~/.ssh/authorized_keys on the server).
The donjon command
donjon -- a no-frills credentials store client ramblings: $ donjon same as "donjon get". $ donjon get print all available credentials in a format suitable for a shell 'export' and 'env' commands. $ donjon get <key> print the value of a single credential. dev/server incantations: $ donjon init [url] clone the repository to ~/.donjon $ donjon update updates the credential store $ donjon set <key> <value> add a key/value pair to the store ("nil" value deletes); repeat to store multiple pairs. $ donjon peer add [name] [key] authorise a new (human) user for writing (the dev commands may commit and push to a Git repository)
require 'donjon' donjon.get #=> Hash of all credentials donjon[key] #=> value of a single credential