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
yuuji.yaginuma 1677c64ee0 Allow to edit secrets in mutiple apps at the same time
In encrypted secrets, the tmp file is used as a fixed file
(`secrets.yml.enc` under the tmp directory).
And that tmp file will be removed after process.

Therefore, if edit secrets at the same time with multiple applications,
the tmp file was conflicting.

In order to avoid the above issue, added pid to tmp file.
2017-07-13 08:06:32 +09:00

121 lines
3 KiB
Ruby

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 generate_key
SecureRandom.hex(OpenSSL::Cipher.new(@cipher).key_len)
end
def key
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
def template
<<-end_of_template.strip_heredoc
# See `secrets.yml` for tips on generating suitable keys.
# production:
# external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289
end_of_template
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
def read_template_for_editing(&block)
writing(template, &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