DEV Community

Cover image for Encrypted Runtime Settings for Ruby and Elixir
Kieran
Kieran

Posted on

Encrypted Runtime Settings for Ruby and Elixir

This will both be an introduction to the library as well as a guide for setting up SharedSettings on Phoenix and Rails. If you don't care about the motivation behind SharedSettings, you can safely skip to the next section.

While migrating some services from Rails to Phoenix, we had a need to keep some settings in-sync between apps. There was a system of uploading secrets to a secure location which the apps would consume, but the new secrets wouldn't take effect until next app boot (read: deploy).

After some hassle with this old process, I decided to create a library that would allow for simple management of runtime settings while keeping both apps in-sync. Once a proof of concept was completed, I realized that this was also useful for controlling standalone apps. That's why I've released SharedSettings as an Elixir package, a Ruby Gem, and a UI library.

I created SharedSettings with these goals in mind:

  1. Simple - API should be straightforward with an interface non-devs could use
  2. Extendable - it should be easy to create new storage adapters or port SharedSettings to other languages
  3. Secure - it should be able to store secrets if needed

Usage

Elixir <> Ruby interop with encryption and UI will be covered in this section, but this will still be valuable if you only care about setting up one app.

Prerequisites

  • A Phoenix project
  • A Rails project
  • redis running locally

Phoenix

Package link

Phoenix will be configured to use SharedSettings, but it will also be the host of the SharedSettings UI. If you are using Elixir <> Ruby interop, it's recommended that the UI is hosted on Elixir.

To get started, add the following to your mix.exs:

{:redix, "~> 0.9"},
{:shared_settings, "~> 0.2.0"},
{:shared_settings_ui, "~> 0.2.0"}
Enter fullscreen mode Exit fullscreen mode

Once you've fetched those deps, we need to add some shared_settings configuration to config.exs (or similar):

config :shared_settings,
  cache_ttl: 300, # Set to 0 to disable
  cache_adapter: SharedSettings.Cache.EtsStore,
  storage_adapter: SharedSettings.Persistence.Redis,
  encryption_key: "...",
  redis: [ # Can also take a URI string
    host: "localhost",
    port: 6379,
    database: 0
  ]
Enter fullscreen mode Exit fullscreen mode

As you see, we need to provide an encryption key if we want to encrypt settings. This is optional, but for the purpose of this guide we'll generate a key with mix SharedSettings.CreateKey. Make sure you keep this key in a secure place and don't check it into version control!

We're almost done! If you wanted to use SharedSettings without UI, this is where you'd stop. Since we want the UI, we add the following to our router.ex:

  scope path: "/shared-settings" do
    # Namespace must be the full route's namespace with no leading or trailing slash
    forward "/", SharedSettings.UI.Router, namespace: "shared-settings"
  end
Enter fullscreen mode Exit fullscreen mode

As it stands, this UI would be accessible to the outside world. Security is up to you and can be as simple as putting this through a pipeline with basic auth.

That's it! You can see the dashboard at /shared-settings and use it to create, update, and destroy settings. Use SharedSettings.get/1 within your application code to access your settings (see the docs for more).

Rails

Gem link

Since the UI is set up on Elixir, we only have to concern ourselves with setting up the core library. You only have to configure the UI library once.

To start, add the following to your Gemfile:

gem 'redis', '~> 4.0'
gem 'shared-settings'
Enter fullscreen mode Exit fullscreen mode

Next, create an initializer at config/initializers/shared_settings.rb with the following contents:

@client = Redis.new # Points to same Redis instance used by Elixir
@adapter = SharedSettings::Persistence::Redis.new(@client)

SharedSettings.configure do |config|
  config.default { SharedSettings.new(@adapter) }
  config.encryption_key = '...' # Same key that's used in Elixir
end
Enter fullscreen mode Exit fullscreen mode

That's it! Now any setting created by either app can also be read by either app. Encrypted settings are accessible by either app and the UI can be used to manage it all.

Extending

If you want to use something other than Redis for storage, that's easy to do! Check out the Store behaviour in the Elixir app to see how to create an interface for something like Postgres or Mongo. If you're using only Elixir, a DETS adapter would be a great fit to keep your dependencies down.

Top comments (0)