Diffing Rails credentials in Rails 6.0

rails secrets

Rails credentials are a pretty nice feature to deal with secrets in your Rails application. Instead of setting a whole list of secrets in your server’s environment variables, you only have to set the decryption key RAILS_MASTER_KEY (or RAILS_PRODUCTION_KEY if you are using environment-specific credentials).

Something that can be annoying with Rail’s credentials is that it is hard to resolve merge-conflicts within them or to compare your current credentials to some other branch’s credentials since git’s diff will only show you blobs.

Since Rails version 6.1 there is a credential diff task which will help with this, but I am working on a Rails 6.0 project right now and there are some dependency issues that don’t allow us to upgrade yet.

Some people have suggested a .gitattributes based diffing option, but that didn’t play nice for me with multi-environment credentials.

This led to me writing the following script:

#!/usr/bin/env ruby
require 'open3'

def branch_exists?(branch_name)
o, s = Open3.capture2("git rev-parse --verify #{branch_name}")


def write_credentials_to_file(credentials_path, key_path, target_path)
o, s = Open3.capture2("bin/rails encrypted:show --key #{key_path} #{credentials_path}")
File.write(target_path, o)

def write_credential_to_file(environment, target_path, credentials_base: "config/credentials")
write_credentials_to_file("#{credentials_base}/#{environment}.yml.enc", "config/credentials/#{environment}.key", target_path)

def compare_conflicted_credentials(environment)
`git checkout --ours config/credentials/#{environment}.yml.enc`
write_credential_to_file(environment, 'tmp/ours')

`git checkout --theirs config/credentials/#{environment}.yml.enc`
write_credential_to_file(environment, 'tmp/theirs')

puts "\n\n\n"
puts "'Ours' is what is in master, 'theirs' is what is in your branch'"
puts "\n\n\n"
puts `icdiff tmp/ours tmp/theirs`

`rm tmp/ours`
`rm tmp/theirs`

def compare_to_branch(environment, branch_name)
write_credential_to_file(environment, 'tmp/your_branch')

`mkdir -p tmp/credentials`
`git show #{branch_name}:config/credentials/#{environment}.yml.enc > tmp/credentials/#{environment}.yml.enc`
write_credential_to_file(environment, 'tmp/other_branch', credentials_base: 'tmp/credentials')

puts `icdiff tmp/your_branch tmp/other_branch`

`rm tmp/your_branch`
`rm tmp/other_branch`
`rm -r tmp/credentials`

cred_path = ARGV[0]
raise 'USAGE: diff_my_creds.sh PATH_TO_CREDS' if cred_path.nil?

environment = cred_path.match(/config\/credentials\/([a-z]+).yml.enc/)[1]
raise "CANNOT DETERMINE ENVIRONMENT FROM #{cred_path}" if environment.nil?

compare_to_branch = ARGV[1] if ARGV[1]

if compare_to_branch
raise "GIVEN BRANCH DOES NOT EXIST" unless branch_exists?(compare_to_branch)

compare_to_branch(environment, compare_to_branch)

`rails credentials:edit -e #{environment}`

Put this in bin/diff_my_creds.sh and do chmod +x bin/diff_my_creds.sh to make it runnable.

Usage when you have a conflict on lets say config/credentials/test.yml.enc:

bin/diff_my_creds.sh config/credentials/test.yml.enc

Usage when you want to diff with a branch:

bin/diff_my_creds.sh config/credentials/test.yml.enc master # or any other branch name than 'master'


tmp/your_branch                                         tmp/other_branch
access_key_id: 123 access_key_id: 123
secret_access_key: 345 secret_access_key: 345

some_service: some_service:
api_key: abc api_key: abc
nice_day_to: diff

A dependency for showing the diff in this way is icdiff which you can install with brew install icdiff, but you could replace the diff command with anything you want, the basic idea stays the same.