# EJSON::Rails [![Build Status](https://github.com/Shopify/ejson-rails/workflows/CI/badge.svg?branch=main)](https://github.com/Shopify/ejson-rails/actions?query=branch%3Amain) Automatically injects [`ejson`](https://github.com/Shopify/ejson) decrypted secrets into your `Rails.application.secrets`. ## Installation Add this line to your application's Gemfile: ```ruby gem 'ejson-rails' ``` And then execute: $ bundle Or install it yourself as: $ gem install ejson-rails ## Usage Decrypted secrets and credentials from `project/config/secrets.json` (or `project/config/secrets.{current_rails_environment}.json` if that doesn't exist) will be accessible via `Rails.application.secrets`. For example: `# project/config/secrets.json` ```json { "some_secret": "key" } ``` will be accessible via `Rails.application.secrets.some_secret` or `Rails.application.secrets[:some_secret]` upon booting. JSON files are loaded once and contents are `deep_merge`'d into your app's existing rails secrets. Secrets will also be accessible via `Rails.application.credentials`, e.g. `Rails.application.credentials.some_secret` or `Rails.application.credentials[:some_secret]`. To avoid subtle compatibility issues, if a credential already exists, an error will occur. If you set the `EJSON_RAILS_DELETE_SECRETS` environment variable to `true` the gem will automatically delete the secrets from the filesystem after loading them into Rails. It will delete both paths (`project/config/secrets.json` and `project/config/secrets.{current_rails_environment}.json`) if the files exist and are writable. NOTE: This gem does not decrypt ejson for you. You will need to configure this as part of your deployment pipeline. ## Migrating to credentials Rails 7.1 has deprecated application secrets in favor of credentials. ejson-rails can migrate secrets to application credentials. Even before running Rails 7.1, you can migrate your secrets in several steps: 1. Convert secrets from YAML to JSON 2. Move any ERB embedded within the YAML to the corresponding environment file 3. Use `Rails.application.credentials` in place of Rails secrets ### 1. Convert secrets from config/secrets.yml to config/secrets.json Typically, secrets share the same structure across different environments. While test secrets are often placeholders, development secrets may sometimes use environment variables to communicate with external services. In that case, the easiest way to migrate is to use the test secrets in all local environments, and override for development as needed: ```sh-session # Recommended bin/rails runner -e test 'Rails.root.join("config/secrets.json").write(JSON.pretty_generate(Rails.application.secrets.to_h.without(:secret_key_base)))' ``` > [!NOTE] > Alternatively, if its necessary to configure distinct values between the development/test environment, you can use separate JSON files for the development/test environments: > > ```sh-session > bin/rails runner 'Rails.root.join("config/secrets.#{Rails.env}.json").write(JSON.pretty_generate(Rails.application.secrets.to_h.without(:secret_key_base)))' > bin/rails runner -e test 'Rails.root.join("config/secrets.#{Rails.env}.json").write(JSON.pretty_generate(Rails.application.secrets.to_h.without(:secret_key_base)))' > ``` ### 2. Move any ERB into the corresponding environment files YAML supports ERB while JSON secrets do not. If your secrets contain ERB, you will need to move that logic to the corresponding environment file: **Before**: `config/secrets.yml` ```yaml development: some_external_service: api_token: <%= ENV.fetch(SOME_EXTERNAL_SERVICE_API_TOKEN, "12345") %> ``` **After**: `config/secrets.json` as generated by the *recommended* command above. ```json { "some_external_service": { "api_token": "12345" }, "something_else_entirely": "abc" } ``` `config/environments/development.rb` ```ruby Rails.application.configure do # elided credentials.some_external_service.api_token = ENV.fetch("SOME_EXTERNAL_SERVICE_API_TOKEN", "12345") credentials.something_else_entirely = ENV.fetch("SOMETHING_ELSE_ENTIRELY", "abc") end ``` #### Rails 7.0 Note > [!NOTE] > In Rails 7.0, credentials are accessed as a Hash with [] and []=.. This is important because the dynamic accessor methods will set values in a different object, and credentials will behave inconsistently after that: ```ruby Rails.application.credentials.some_external_service.api_token = "foo" Rails.application.credentials[:some_external_service][:api_token] # => "12345" ``` Also note the code sets top-level values through `credentials.config`, because `credentials#[]=(key, value)` sets values in a different object. ```ruby Rails.application.credentials[:something_else_entirely] = "foo" Rails.application.credentials[:something_else_entirely] # => "abc" ``` Make sure there's no code using the dynamic accessors before setting the configuration in the Hash, or the values won't be accessible from the dynamic accessor: ```ruby Rails.application.credentials.something_else_entirely # just accessing is enough to cause the issue Rails.application.credentials[:some_external_service][:api_token] = "foo" Rails.application.credentials.some_external_service.api_token # => "12345" ``` ### 3. Use `Rails.application.credentials` You are now ready to replace Rails secrets with Rails credentials: ```sh-session git ls-files | xargs ruby -pi -e 'gsub("Rails.application.secrets", "Rails.application.credentials")' -- ``` To avoid the deprecation warning from the use of secrets in `ejson-rails` once you're running Rails 7.1, require another file from your Gemfile: ```ruby gem 'ejson-rails', require: 'ejson/rails/skip_secrets' ``` With this require, ejson-rails will no longer merge secrets from JSON into `Rails.application.secrets`. This will be the default in the next major version. ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/ejson-rails. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).