未验证 提交 68479d09 编写于 作者: K Kasper Timm Hansen

Merge branch 'freeletics-manage-multiple-credential-files'

Fixes https://github.com/rails/rails/pull/30940
......@@ -433,10 +433,41 @@ def secret_key_base
# the Rails master key, which is either taken from ENV["RAILS_MASTER_KEY"] or from loading
# `config/master.key`.
def credentials
@credentials ||= ActiveSupport::EncryptedConfiguration.new \
config_path: Rails.root.join("config/credentials.yml.enc"),
key_path: Rails.root.join("config/master.key"),
env_key: "RAILS_MASTER_KEY"
@credentials ||= encrypted("config/credentials.yml.enc")
end
# Shorthand to decrypt any encrypted configurations or files.
#
# For any file added with `bin/rails encrypted:edit` call `read` to decrypt
# the file with the master key.
# The master key is either stored in `config/master.key` or `ENV["RAILS_MASTER_KEY"]`.
#
# Rails.application.encrypted("config/mystery_man.key").read
# # => "We've met before, haven't we?"
#
# It's also possible to interpret encrypted YAML files with `config`.
#
# Rails.application.encrypted("config/credentials.yml.enc").config
# # => { next_guys_line: "I don't think so. Where was it you think we met?" }
#
# Any top-level configs are also accessible directly on the return value:
#
# Rails.application.encrypted("config/credentials.yml.enc").next_guys_line
# # => "I don't think so. Where was it you think we met?"
#
# The files or configs can also be encrypted with a custom key. To decrypt with
# a key in the `ENV`, use:
#
# Rails.application.encrypted("config/special_tokens.yml.enc", env_key: "SPECIAL_TOKENS")
#
# Or to decrypt with a file, that should be version control ignored, relative to `Rails.root`:
#
# Rails.application.encrypted("config/special_tokens.yml.enc", key_path: "config/special_tokens.key")
def encrypted(path, key_path: "config/master.key", env_key: "RAILS_MASTER_KEY")
ActiveSupport::EncryptedConfiguration.new \
config_path: Rails.root.join(path),
key_path: Rails.root.join(key_path),
env_key: env_key
end
def to_app #:nodoc:
......
require "active_support/encrypted_file"
module Rails
module Command
module Helpers
module Editor
private
def ensure_editor_available(command:)
if ENV["EDITOR"].to_s.empty?
say "No $EDITOR to open file in. Assign one like this:"
say ""
say %(EDITOR="mate --wait" #{command})
say ""
say "For editors that fork and exit immediately, it's important to pass a wait flag,"
say "otherwise the credentials will be saved immediately with no chance to edit."
false
else
true
end
end
def catch_editing_exceptions
yield
rescue Interrupt
say "Aborted changing file: nothing saved."
rescue ActiveSupport::EncryptedFile::MissingKeyError => error
say error.message
end
end
end
end
end
# frozen_string_literal: true
require "active_support"
require "rails/command/helpers/editor"
module Rails
module Command
class CredentialsCommand < Rails::Command::Base # :nodoc:
include Helpers::Editor
no_commands do
def help
say "Usage:\n #{self.class.banner}"
......@@ -16,41 +19,25 @@ def help
def edit
require_application_and_environment!
ensure_editor_available || (return)
ensure_editor_available(command: "bin/rails credentials:edit") || (return)
ensure_master_key_has_been_added
ensure_credentials_have_been_added
catch_editing_exceptions do
change_credentials_in_system_editor
end
say "New credentials encrypted and saved."
rescue Interrupt
say "Aborted changing credentials: nothing saved."
rescue ActiveSupport::EncryptedFile::MissingKeyError => error
say error.message
end
def show
require_application_and_environment!
say Rails.application.credentials.read.presence ||
"No credentials have been added yet. Use bin/rails credentials:edit to change that."
end
private
def ensure_editor_available
if ENV["EDITOR"].to_s.empty?
say "No $EDITOR to open credentials in. Assign one like this:"
say ""
say %(EDITOR="mate --wait" bin/rails credentials:edit)
say ""
say "For editors that fork and exit immediately, it's important to pass a wait flag,"
say "otherwise the credentials will be saved immediately with no chance to edit."
false
else
true
end
end
def ensure_master_key_has_been_added
master_key_generator.add_master_key_file
master_key_generator.ignore_master_key_file
......
# frozen_string_literal: true
require "pathname"
require "active_support"
require "rails/command/helpers/editor"
module Rails
module Command
class EncryptedCommand < Rails::Command::Base # :nodoc:
include Helpers::Editor
class_option :key, aliases: "-k", type: :string,
default: "config/master.key", desc: "The Rails.root relative path to the encryption key"
no_commands do
def help
say "Usage:\n #{self.class.banner}"
say ""
end
end
def edit(file_path)
require_application_and_environment!
ensure_editor_available(command: "bin/rails encrypted:edit") || (return)
ensure_encryption_key_has_been_added(options[:key])
ensure_encrypted_file_has_been_added(file_path, options[:key])
catch_editing_exceptions do
change_encrypted_file_in_system_editor(file_path, options[:key])
end
say "File encrypted and saved."
rescue ActiveSupport::MessageEncryptor::InvalidMessage
say "Couldn't decrypt #{file_path}. Perhaps you passed the wrong key?"
end
def show(file_path)
require_application_and_environment!
say Rails.application.encrypted(file_path, key_path: options[:key]).read.presence ||
"File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that."
end
private
def ensure_encryption_key_has_been_added(key_path)
encryption_key_file_generator.add_key_file(key_path)
encryption_key_file_generator.ignore_key_file(key_path)
end
def ensure_encrypted_file_has_been_added(file_path, key_path)
encrypted_file_generator.add_encrypted_file_silently(file_path, key_path)
end
def change_encrypted_file_in_system_editor(file_path, key_path)
Rails.application.encrypted(file_path, key_path: key_path).change do |tmp_path|
system("#{ENV["EDITOR"]} #{tmp_path}")
end
end
def encryption_key_file_generator
require "rails/generators"
require "rails/generators/rails/encryption_key_file/encryption_key_file_generator"
Rails::Generators::EncryptionKeyFileGenerator.new
end
def encrypted_file_generator
require "rails/generators"
require "rails/generators/rails/encrypted_file/encrypted_file_generator"
Rails::Generators::EncryptedFileGenerator.new
end
end
end
end
# frozen_string_literal: true
require "rails/generators/base"
require "active_support/encrypted_file"
module Rails
module Generators
class EncryptedFileGenerator < Base
def add_encrypted_file(file_path, key_path)
unless File.exist?(file_path)
say "Adding #{file_path} to store encrypted content."
say ""
say "The following content has been encrypted with the encryption key:"
say ""
say template, :on_green
say ""
add_encrypted_file_silently(file_path, key_path)
say "You can edit encrypted file with `bin/rails encrypted:edit #{file_path}`."
say ""
end
end
def add_encrypted_file_silently(file_path, key_path, template = encrypted_file_template)
unless File.exist?(file_path)
setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY" }
ActiveSupport::EncryptedFile.new(setup).write(template)
end
end
private
def encrypted_file_template
"# aws:\n# access_key_id: 123\n# secret_access_key: 345\n\n"
end
end
end
end
# frozen_string_literal: true
require "pathname"
require "rails/generators/base"
require "active_support/encrypted_file"
module Rails
module Generators
class EncryptionKeyFileGenerator < Base
def add_key_file(key_path)
key_path = Pathname.new(key_path)
unless key_path.exist?
key = ActiveSupport::EncryptedFile.generate_key
log "Adding #{key_path} to store the encryption key: #{key}"
log ""
log "Save this in a password manager your team can access."
log ""
log "If you lose the key, no one, including you, can access anything encrypted with it."
log ""
add_key_file_silently(key_path, key)
log ""
end
end
def add_key_file_silently(key_path, key = nil)
create_file key_path, key || ActiveSupport::EncryptedFile.generate_key
end
def ignore_key_file(key_path, ignore: key_ignore(key_path))
if File.exist?(".gitignore")
unless File.read(".gitignore").include?(ignore)
log "Ignoring #{key_path} so it won't end up in Git history:"
log ""
append_to_file ".gitignore", ignore
log ""
end
else
log "IMPORTANT: Don't commit #{key_path}. Add this to your ignore file:"
log ignore, :on_green
log ""
end
end
private
def key_ignore(key_path)
[ "", "/#{key_path}", "" ].join("\n")
end
end
end
end
# frozen_string_literal: true
require "rails/generators/base"
require "pathname"
require "rails/generators/base"
require "rails/generators/rails/encryption_key_file/encryption_key_file_generator"
require "active_support/encrypted_file"
module Rails
......@@ -20,31 +21,24 @@ def add_master_key_file
log "If you lose the key, no one, including you, can access anything encrypted with it."
log ""
add_master_key_file_silently key
add_master_key_file_silently(key)
log ""
end
end
def add_master_key_file_silently(key = nil)
create_file MASTER_KEY_PATH, key || ActiveSupport::EncryptedFile.generate_key
key_file_generator.add_key_file_silently(MASTER_KEY_PATH, key)
end
def ignore_master_key_file
if File.exist?(".gitignore")
unless File.read(".gitignore").include?(key_ignore)
log "Ignoring #{MASTER_KEY_PATH} so it won't end up in Git history:"
log ""
append_to_file ".gitignore", key_ignore
log ""
end
else
log "IMPORTANT: Don't commit #{MASTER_KEY_PATH}. Add this to your ignore file:"
log key_ignore, :on_green
log ""
end
key_file_generator.ignore_key_file(MASTER_KEY_PATH, ignore: key_ignore)
end
private
def key_file_generator
EncryptionKeyFileGenerator.new
end
def key_ignore
[ "", "# Ignore master key for decrypting credentials and more.", "/#{MASTER_KEY_PATH}", "" ].join("\n")
end
......
......@@ -13,7 +13,10 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
teardown { teardown_app }
test "edit without editor gives hint" do
assert_match "No $EDITOR to open credentials in", run_edit_command(editor: "")
run_edit_command(editor: "").tap do |output|
assert_match "No $EDITOR to open file in", output
assert_match "bin/rails credentials:edit", output
end
end
test "edit credentials" do
......
# frozen_string_literal: true
require "isolation/abstract_unit"
require "env_helpers"
require "rails/command"
require "rails/commands/encrypted/encrypted_command"
class Rails::Command::EncryptedCommandTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation, EnvHelpers
setup :build_app
teardown :teardown_app
test "edit without editor gives hint" do
run_edit_command("config/tokens.yml.enc", editor: "").tap do |output|
assert_match "No $EDITOR to open file in", output
assert_match "bin/rails encrypted:edit", output
end
end
test "edit encrypted file" do
# Run twice to ensure file can be reread after first edit pass.
2.times do
assert_match(/access_key_id: 123/, run_edit_command("config/tokens.yml.enc"))
end
end
test "edit command does not add master key to gitignore when already exist" do
run_edit_command("config/tokens.yml.enc")
Dir.chdir(app_path) do
assert_match "/config/master.key", File.read(".gitignore")
end
end
test "edit encrypts file with custom key" do
run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")
Dir.chdir(app_path) do
assert File.exist?("config/tokens.yml.enc")
assert File.exist?("config/tokens.key")
assert_match "/config/tokens.key", File.read(".gitignore")
end
assert_match(/access_key_id: 123/, run_edit_command("config/tokens.yml.enc", key: "config/tokens.key"))
end
test "show encrypted file with custom key" do
run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")
assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key"))
end
test "won't corrupt encrypted file when passed wrong key" do
run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")
assert_match "passed the wrong key",
run_edit_command("config/tokens.yml.enc", allow_failure: true)
assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key"))
end
private
def run_edit_command(file, key: nil, editor: "cat", **options)
switch_env("EDITOR", editor) do
rails "encrypted:edit", prepare_args(file, key), **options
end
end
def run_show_command(file, key: nil)
rails "encrypted:show", prepare_args(file, key)
end
def prepare_args(file, key)
args = [ file ]
args.push("--key", key) if key
args
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册