1740b1f2cb
Prior to this commit, if `config.require_master_key` was set to true in `config/application.rb`, the `credentials:edit` command could not automatically generate a master key file: ```ruby # In config/application.rb config.require_master_key = true ``` ```console # No previously existing credentials $ rm config/master.key config/credentials.yml.enc ``` ```console $ bin/rails credentials:edit activesupport/lib/active_support/encrypted_file.rb:118:in `handle_missing_key': Missing encryption key to decrypt file with. Ask your team for your master key and write it to config/master.key or put it in the ENV['RAILS_MASTER_KEY']. (ActiveSupport::EncryptedFile::MissingKeyError) ``` This commit adds a `EncryptedFile#key?` method that can be used to check for the existence of a key without raising `MissingKeyError`, and the `credentials:edit` command now uses this method: ```console $ bin/rails credentials:edit Adding config/master.key to store the encryption key: ... Save this in a password manager your team can access. If you lose the key, no one, including you, can access anything encrypted with it. create config/master.key ``` This commit also applies the same fix to the `encrypted:edit` command.
153 lines
4.1 KiB
Ruby
153 lines
4.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "abstract_unit"
|
|
require "active_support/encrypted_file"
|
|
|
|
class EncryptedFileTest < ActiveSupport::TestCase
|
|
setup do
|
|
@original_env_content_key = ENV["CONTENT_KEY"]
|
|
ENV["CONTENT_KEY"] = nil
|
|
|
|
@content = "One little fox jumped over the hedge"
|
|
|
|
@tmpdir = Dir.mktmpdir("encrypted-file-test-")
|
|
@content_path = File.join(@tmpdir, "content.txt.enc")
|
|
|
|
@key = ActiveSupport::EncryptedFile.generate_key
|
|
@key_path = File.join(@tmpdir, "content.txt.key")
|
|
File.write(@key_path, @key)
|
|
|
|
@encrypted_file = encrypted_file(@content_path)
|
|
end
|
|
|
|
teardown do
|
|
FileUtils.rm_rf @content_path
|
|
FileUtils.rm_rf @key_path
|
|
FileUtils.rm_rf @tmpdir
|
|
|
|
ENV["CONTENT_KEY"] = @original_env_content_key
|
|
end
|
|
|
|
test "reading content by env key" do
|
|
FileUtils.rm_rf @key_path
|
|
|
|
ENV["CONTENT_KEY"] = @key
|
|
@encrypted_file.write @content
|
|
|
|
assert_equal @content, @encrypted_file.read
|
|
end
|
|
|
|
test "reading content by key file" do
|
|
@encrypted_file.write(@content)
|
|
assert_equal @content, @encrypted_file.read
|
|
end
|
|
|
|
test "change content by key file" do
|
|
@encrypted_file.write(@content)
|
|
@encrypted_file.change do |file|
|
|
file.write(file.read + " and went by the lake")
|
|
end
|
|
|
|
assert_equal "#{@content} and went by the lake", @encrypted_file.read
|
|
end
|
|
|
|
test "raise MissingKeyError when key is missing" do
|
|
assert_raise ActiveSupport::EncryptedFile::MissingKeyError do
|
|
encrypted_file(@content_path, key_path: "", env_key: "").read
|
|
end
|
|
end
|
|
|
|
test "raise MissingKeyError when env key is blank" do
|
|
FileUtils.rm_rf @key_path
|
|
|
|
ENV["CONTENT_KEY"] = ""
|
|
raised = assert_raise ActiveSupport::EncryptedFile::MissingKeyError do
|
|
@encrypted_file.write @content
|
|
@encrypted_file.read
|
|
end
|
|
|
|
assert_match(/Missing encryption key to decrypt file/, raised.message)
|
|
end
|
|
|
|
test "key can be added after MissingKeyError raised" do
|
|
FileUtils.rm_rf @key_path
|
|
|
|
assert_raise ActiveSupport::EncryptedFile::MissingKeyError do
|
|
@encrypted_file.key
|
|
end
|
|
|
|
File.write(@key_path, @key)
|
|
|
|
assert_nothing_raised do
|
|
assert_equal @key, @encrypted_file.key
|
|
end
|
|
end
|
|
|
|
test "key? is true when key file exists" do
|
|
assert @encrypted_file.key?
|
|
end
|
|
|
|
test "key? is true when env key is present" do
|
|
FileUtils.rm_rf @key_path
|
|
ENV["CONTENT_KEY"] = @key
|
|
|
|
assert @encrypted_file.key?
|
|
end
|
|
|
|
test "key? is false and does not raise when the key is missing" do
|
|
FileUtils.rm_rf @key_path
|
|
|
|
assert_nothing_raised do
|
|
assert_not @encrypted_file.key?
|
|
end
|
|
end
|
|
|
|
test "raise InvalidKeyLengthError when key is too short" do
|
|
File.write(@key_path, ActiveSupport::EncryptedFile.generate_key[0..-2])
|
|
|
|
assert_raise ActiveSupport::EncryptedFile::InvalidKeyLengthError do
|
|
@encrypted_file.write(@content)
|
|
end
|
|
end
|
|
|
|
test "raise InvalidKeyLengthError when key is too long" do
|
|
File.write(@key_path, ActiveSupport::EncryptedFile.generate_key + "0")
|
|
|
|
assert_raise ActiveSupport::EncryptedFile::InvalidKeyLengthError do
|
|
@encrypted_file.write(@content)
|
|
end
|
|
end
|
|
|
|
test "respects existing content_path symlink" do
|
|
@encrypted_file.write(@content)
|
|
|
|
symlink_path = File.join(@tmpdir, "content_symlink.txt.enc")
|
|
File.symlink(@encrypted_file.content_path, symlink_path)
|
|
|
|
encrypted_file(symlink_path).write(@content)
|
|
|
|
assert File.symlink?(symlink_path)
|
|
assert_equal @content, @encrypted_file.read
|
|
ensure
|
|
FileUtils.rm_rf symlink_path
|
|
end
|
|
|
|
test "creates new content_path symlink if it's dead" do
|
|
symlink_path = File.join(@tmpdir, "content_symlink.txt.enc")
|
|
File.symlink(@content_path, symlink_path)
|
|
|
|
encrypted_file(symlink_path).write(@content)
|
|
|
|
assert File.exist?(@content_path)
|
|
assert_equal @content, @encrypted_file.read
|
|
ensure
|
|
FileUtils.rm_rf symlink_path
|
|
end
|
|
|
|
private
|
|
def encrypted_file(content_path, key_path: @key_path, env_key: "CONTENT_KEY")
|
|
ActiveSupport::EncryptedFile.new(content_path: @content_path, key_path: key_path,
|
|
env_key: env_key, raise_if_missing_key: true)
|
|
end
|
|
end
|