Rails 5.2 Custom Credentials — generally accepted way to segregate environments?

In some apps I’ve worked on Rails 5.1 and prior, environment variables, saved directly into the source code.

In rails 5.2 Custom credentials encourages us to check-in only the encrypted version of our configuration, and keep our master.key keyfile outside of our repository.

My question is this: Is there a way to segregate by environment? (i.e., development, staging, production?)

seems like the instructions for setting up AWS keys, for example, would have the dev, staging + production all pointing to & using the same AWS bucket, access key, and secret. But it seems like for many services I’d want to have different credentials for different environments.

I found this SO post that discusses this question, but unfortunately it doesn’t present a very good answer IMHO because the there are only two answers: 1) I don’t quite understand and 2) a suggestion to basically check all your ENV variables against each of your environments, which seems like it could encourage a messy setup. I much like answer #1 from this SO post, but I don’t understand how to implement it practically.

https://stackoverflow.com/questions/53642152/how-to-manage-credentials-for-different-environments-in-rails-5-2

any tip appreciated.

Thanks,

Jason

Rails 6 will have this feature https://github.com/rails/rails/pull/33521

For Rails 5.2, personally, I wouldn’t add the file to the source control. I would do this steps:

1- run rails credentials:edit locally

2- add the credentials for production and save

3- upload the file to your hosting at /home/user/your_app/shared/config/

4- configure capistrano to symlink that file on each deploy (at config/deploy.rb)

set :linked_files, fetch(:linked_files, [])+%W{config/credentials.yml.enc}

Now, on each deploy, capistrano runs a task that adds some symlink to the current release pointing to /shared so they are kept between releases. Your file /home/user/your_app/current/config/credentials.yml.enc will actually be a symlink to /home/user/your_app/shared/config/credentials.yml.enc. You can just have that on production, use one on development and add it to the .gitignore file so it doesn’t conflict with the symlink.

Rails 6 will have this feature https://github.com/rails/rails/pull/33521

For Rails 5.2, personally, I wouldn’t add the file to the source control. I would do this steps:

1- run rails credentials:edit locally

2- add the credentials for production and save

3- upload the file to your hosting at /home/user/your_app/shared/config/

You mean, you don’t even check-in the encrypted file?

4- configure capistrano to symlink that file on each deploy (at config/deploy.rb)

set :linked_files, fetch(:linked_files, [])+%W{config/credentials.yml.enc}

Now, on each deploy, capistrano runs a task that adds some symlink to the current release pointing to /shared so they are kept between releases. Your file /home/user/your_app/current/config/credentials.yml.enc will actually be a symlink to /home/user/your_app/shared/config/credentials.yml.enc. You can just have that on production, use one on development and add it to the .gitignore file so it doesn’t conflict with the symlink.

I think this is interesting but sort of paradigmatically different as I am working with 12-Factor deploys (Heroku), so there isn’t a symlink paradigm in these cases.

nonetheless, thanks for the input.

-Jason

Hmmmm I don’t know if heroku lets you add custom commands during deploy, I’ve only used it for tests. If it does, you could have two files on your source credentials.yml.enc.prod and credentials.yml.enc.dev and during deploy just rename on of them depending on the environment you are (mv config/credentials.yml.enc config/credentials.yml.enc). It you can’t add custom command then I don’t know haha.

I’ve found the rails-env-credentials
gem useful for this. Not sure how compatible it will be with the upcoming Rails 6 support but its been working great for my app.

Eric

for rails 5, just nest environment specific credentials under a key for said environment

then all you have to do when accessing said config is add the environment to the party that pulls in config, e.g.:

credentials.yml.enc


main_key:

development:

sub_key:

value_key: 'development'

production:

sub_key:

value_key: 'production'

test:

sub_key:

value_key: 'test'

code:


my_credential = Rails.application.credentials.dig(:main_key, Rails.env.to_sym, :subkey, :value_key)`

for rails 5, just nest environment specific credentials under a key for said environment

then all you have to do when accessing said config is add the environment to the party that pulls in config, e.g.:

credentials.yml.enc:

main_key:
development:
sub_key:
value_key: ‘development’
production:
sub_key:
value_key: ‘production’
test:
sub_key:
value_key: ‘test’

``

code:

my_credential = Rails.application.credentials.dig(:main_key, Rails.env.to_sym, :sub_key, :value_key)

``

I think this solution seems the most Rails 5.2 friendly, but ideally isn’t the perfect solution as ideally you could in fact (one day, in Rails 6, say), separate the 3 files themselves, so you could, say, give junior developers access to only development+staging credentials by giving the only the keys for the development + staging master keys (but continuing to check-in all 3 .yml.enc files into source-code. the 3 keys are of course, not checked-in)

But if you have a team where you don’t care if all the devs have the same (full) credentials then I like this solution best.

-Jason

In the mean time you can add a rake task to the lib folder to rename credentials, or a bash/Ruby script to the bin folder:

Given: development.yml.enc, test.yml.enc, production.yml.enc

lib/tasks/credentials.rake

namespace : credentials do

namespace :set_env do

task production: : environment do

Rake::Task[‘credentials:set_env’].invoke(‘production’)

end

task test: : environment do

Rake::Task[‘credentials:set_env’].invoke(‘test’)

end

task development: : environment do

Rake::Task[‘credentials:set_env’].invoke(‘development’)

end

end

task :set_env, [ :env_name ] => : environment do |_, args|

File.open(Rails.root.join(‘config’, ‘credentials.yml.enc’), ‘w’) do |f|

f.write(File.read(Rails.root.join(‘config’, “#{args[:env_name]}.ym.enc”)))

end

end

end

For something dynamic you could do

namespace : credentials do

namespace :set_env do

Dir[‘config/*.yml.enc’].each do |path|

f_name = path[/(\w+).yml.enc$/, 1].to_sym

next if f_name == :credentials

task f_name => : environment do

File.open(Rails.root.join(‘config’, ‘credentials.yml.enc’), ‘w’) do |f|

f.write(File.read(path))

end

end

end

end