1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/railties/lib/rails/secrets.rb
Kasper Timm Hansen bb30f05f38
Deprecate encrypted secrets in favor of credentials.
Allow edits of existing encrypted secrets generated on Rails 5.1,
but refer to credentials when attempting to setup.

This also removes the need for any of the setup code, so the
generator can be ripped out altogether.
2017-11-12 17:50:09 +01:00

106 lines
2.6 KiB
Ruby

# frozen_string_literal: true
require "yaml"
require "active_support/message_encryptor"
require "active_support/core_ext/string/strip"
module Rails
# Greatly inspired by Ara T. Howard's magnificent sekrets gem. 😘
class Secrets # :nodoc:
class MissingKeyError < RuntimeError
def initialize
super(<<-end_of_message.squish)
Missing encryption key to decrypt secrets with.
Ask your team for your master key and put it in ENV["RAILS_MASTER_KEY"]
end_of_message
end
end
@cipher = "aes-128-gcm"
@root = File # Wonky, but ensures `join` uses the current directory.
class << self
attr_writer :root
def parse(paths, env:)
paths.each_with_object(Hash.new) do |path, all_secrets|
require "erb"
secrets = YAML.load(ERB.new(preprocess(path)).result) || {}
all_secrets.merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
all_secrets.merge!(secrets[env].deep_symbolize_keys) if secrets[env]
end
end
def key
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
def encrypt(data)
encryptor.encrypt_and_sign(data)
end
def decrypt(data)
encryptor.decrypt_and_verify(data)
end
def read
decrypt(IO.binread(path))
end
def write(contents)
IO.binwrite("#{path}.tmp", encrypt(contents))
FileUtils.mv("#{path}.tmp", path)
end
def read_for_editing(&block)
writing(read, &block)
end
private
def handle_missing_key
raise MissingKeyError
end
def read_key_file
if File.exist?(key_path)
IO.binread(key_path).strip
end
end
def key_path
@root.join("config", "secrets.yml.key")
end
def path
@root.join("config", "secrets.yml.enc").to_s
end
def preprocess(path)
if path.end_with?(".enc")
decrypt(IO.binread(path))
else
IO.read(path)
end
end
def writing(contents)
tmp_file = "#{File.basename(path)}.#{Process.pid}"
tmp_path = File.join(Dir.tmpdir, tmp_file)
IO.binwrite(tmp_path, contents)
yield tmp_path
updated_contents = IO.binread(tmp_path)
write(updated_contents) if updated_contents != contents
ensure
FileUtils.rm(tmp_path) if File.exist?(tmp_path)
end
def encryptor
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: @cipher)
end
end
end
end