Version Tests Code Climate GitHub Sponsors

Dry::Credentials

Manage and deploy secrets (access keys, API tokens etc) in encrypted files which can safely be committed to the code repository. To decrypt and and use them, only one environment variable containing the corresponding key is required.

While similar in purpose to ActiveSupport::EncryptedConfiguration, this lightweight implementation doesn't introduce any dependencies.

Thank you for supporting free and open-source software by sponsoring on GitHub or on Donorbox. Any gesture is appreciated, from a single Euro for a ☕️ cup of coffee to 🍹 early retirement.

Install

Security

This gem is cryptographically signed in order to assure it hasn't been tampered with. Unless already done, please add the author's public key as a trusted certificate now:

gem cert --add <(curl -Ls https://raw.github.com/svoop/dry-credentials/main/certs/svoop.pem)

Bundler

Add the following to the Gemfile or gems.rb of your Bundler powered Ruby project:

gem 'dry-credentials'

And then install the bundle:

bundle install --trust-policy MediumSecurity

See Integrations below for how to integrate Dry::Credentials into frameworks.

Usage

Extend any class with Dry::Credentials to use the default settings:

class App
  extend Dry::Credentials
end

The credentials macro allows you to tweak the settings:

class App
  extend Dry::Credentials

  credentials do
    env "sandbox"
    dir "/path/to/credentials"
  end
end

⚠️ The dir must exist and have the proper permissions set.

Now initialize the credentials for this env:

App.credentials.edit!

It creates /path/to/credentials/sandbox.yml.enc (where the encrypted credentials are stored) and opens this file using your favourite editor as per the EDITOR environment variable.

For the sake of this example, let's assume you paste the following credentials:

otp:
  secret_key: ZcikLNiUQoqOo594oH2eqw04HPclhjkpgvpBik/40oU=
  salt: 583506a49c71724a9f085bf2e70362df9d973f08d6575191cab6a177dfb872c6
  meta:
    realm: main

When you close the editor, the credentials are encrypted and stored. This first time only, the key to encrypt and decrypt is printed to STDOUT:

SANDBOX_CREDENTIALS_KEY=68656973716a4e706e336733377245732b6e77584c6c772b5432446532456f674767664271374a623876383d

⚠️ In case you've entered invalid YAML, a warning will be printed and the editor reopens immediately.

To decrypt the credentials and use them in your app, you have to set just this one environment variable containing the key, in this case:

export SANDBOX_CREDENTIALS_KEY=68656973716a4e706e336733377245732b6e77584c6c772b5432446532456f674767664271374a623876383d

With this in place, you can use the decrypted credentials anywhere in your app:

App.credentials.otp.secret_key
# => "ZcikLNiUQoqOo594oH2eqw04HPclhjkpgvpBik/40oU="

App.credentials.otp.meta.realm
# => "main"

Environments

Credentials are isolated into environments which most likely will, but don't necessarily have to align with the environments of the app framework you're using.

By default, the current environment is read from APP_ENV. You shouldn't use RACK_ENV for this, here's why.

⚠️ For safety reasons, don't share the same key across multiple environments!

Reload Credentials

The credentials are lazy loaded when queried for the first time. After that, changes in the encrypted credentials files are not taken into account at runtime for efficiency reasons.

However, you can schedule a reload:

App.credentials.reload!

The reload is not done immediately but the next time credentials are queried.

Edit Credentials

This gem does not provide any CLI tools to edit the credentials. You should integrate it into your app instead e.g. with a Rake task or an extension to the CLI tool of the app framework you're using.

You can explicitly pass the environment to edit:

App.credentials.edit! "production"

Editing credentials implicitly schedules a reload!.

Settings

If you have to, you can access the settings programmatically:

App.credentials[:env]   # => "production"

Defaults

Setting Default Description
env -> { ENV["APP_ENV"] } environment such as development
dir "config/credentials" directory where encrypted credentials are stored
cipher "aes-256-gcm" any of OpenSSL::Cipher.ciphers
digest "sha256" sign digest used if the cipher doesn't support AEAD
serializer Marshal serializer responding to dump and load

Integrations

Bridgetown

The bridgetown_credentials gem integrates Dry::Credentials into your Bridgetown site.

Hanami 2

To use credentials in a Hanami 2 app, first add this gem to the Gemfile of the app and then create a provider config/providers/credentials.rb:

# frozen_string_literal: true

Hanami.app.register_provider :credentials do
  prepare do
    require "dry-credentials"

    Dry::Credentials::Extension.new.then do |credentials|
      credentials[:env] = Hanami.env
      credentials[:dir] = Hanami.app.root.join(credentials[:dir])
      credentials[:dir].mkpath
      credentials.load!
      register "credentials", credentials
    end
  end
end

Next up are Rake tasks lib/tasks/credentials.rake:

namespace :credentials do
  desc "Edit (or create) the encrypted credentials file"
  task :edit, [:env] => [:environment] do |_, args|
    Hanami.app.prepare(:credentials)
    Hanami.app['credentials'].edit! args[:env]
  end
end

(As of Hanami 2.1, you have to explicitly load such tasks in the Rakefile yourself.)

You can now create a new credentials file for the development environment:

rake credentials:edit

This prints the credentials key you have to set in .env:

DEVELOPMENT_CREDENTIALS_KEY=...

The credentials are now available anywhere you inject them:

module MyHanamiApp
  class ApiKeyPrinter
    include Deps[
      "credentials"
    ]

    def call
      puts credentials.api_key
    end
  end
end

You can use the credentials in other providers. Say, you want to pass the ROM database URL (which contains the connection password) using credentials instead of settings. Simply replace target["settings"].database_url with target["credentials"].database_url and you're good to go:

Hanami.app.register_provider :persistence, namespace: true do
  prepare do
    require "rom"

    config = ROM::Configuration.new(:sql, target["credentials"].database_url)

    register "config", config
    register "db", config.gateways[:default].connection
  end

  (...)
end

Finally, if you have trouble using the credentials in slices, you might have to share this app component in config/app.rb:

module MyHanamiApp
  class App < Hanami::App
    config.shared_app_component_keys += ["credentials"]
  end
end

Ruby on Rails

ActiveSupport implements encrypted configuration which is used by rails credentials:edit out of the box. There's no benefit from introducing an additional dependency like Dry::Credentials.

Rodbot

Dry::Credentials is integrated into Rodbot out of the box, see the README for more.

Development

To install the development dependencies and then run the test suite:

bundle install
bundle exec rake    # run tests once
bundle exec guard   # run tests whenever files are modified

You're welcome to join the discussion forum to ask questions or drop feature ideas, submit issues you may encounter or contribute code by forking this project and submitting pull requests.