mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Revert "Revert "Add encrypted secrets""
This commit is contained in:
parent
4734d23c74
commit
fbee4e3ce3
17 changed files with 482 additions and 27 deletions
|
@ -4,6 +4,7 @@ require "active_support/core_ext/object/blank"
|
|||
require "active_support/key_generator"
|
||||
require "active_support/message_verifier"
|
||||
require "rails/engine"
|
||||
require "rails/secrets"
|
||||
|
||||
module Rails
|
||||
# An Engine with the responsibility of coordinating the whole boot process.
|
||||
|
@ -385,18 +386,7 @@ module Rails
|
|||
def secrets
|
||||
@secrets ||= begin
|
||||
secrets = ActiveSupport::OrderedOptions.new
|
||||
yaml = config.paths["config/secrets"].first
|
||||
|
||||
if File.exist?(yaml)
|
||||
require "erb"
|
||||
|
||||
all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
|
||||
shared_secrets = all_secrets["shared"]
|
||||
env_secrets = all_secrets[Rails.env]
|
||||
|
||||
secrets.merge!(shared_secrets.deep_symbolize_keys) if shared_secrets
|
||||
secrets.merge!(env_secrets.deep_symbolize_keys) if env_secrets
|
||||
end
|
||||
secrets.merge! Rails::Secrets.parse(config.paths["config/secrets"].existent, env: Rails.env)
|
||||
|
||||
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
|
||||
secrets.secret_key_base ||= config.secret_key_base
|
||||
|
|
|
@ -2,6 +2,7 @@ require "fileutils"
|
|||
require "active_support/notifications"
|
||||
require "active_support/dependencies"
|
||||
require "active_support/descendants_tracker"
|
||||
require "rails/secrets"
|
||||
|
||||
module Rails
|
||||
class Application
|
||||
|
@ -77,6 +78,11 @@ INFO
|
|||
initializer :bootstrap_hook, group: :all do |app|
|
||||
ActiveSupport.run_load_hooks(:before_initialize, app)
|
||||
end
|
||||
|
||||
initializer :set_secrets_root, group: :all do
|
||||
Rails::Secrets.root = root
|
||||
Rails::Secrets.read_encrypted_secrets = config.read_encrypted_secrets
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,8 @@ module Rails
|
|||
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
|
||||
:ssl_options, :public_file_server,
|
||||
:session_options, :time_zone, :reload_classes_only_on_change,
|
||||
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading
|
||||
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
|
||||
:read_encrypted_secrets
|
||||
|
||||
attr_writer :log_level
|
||||
attr_reader :encoding, :api_only
|
||||
|
@ -51,6 +52,7 @@ module Rails
|
|||
@debug_exception_response_format = nil
|
||||
@x = Custom.new
|
||||
@enable_dependency_loading = false
|
||||
@read_encrypted_secrets = false
|
||||
end
|
||||
|
||||
def encoding=(value)
|
||||
|
@ -80,7 +82,7 @@ module Rails
|
|||
@paths ||= begin
|
||||
paths = super
|
||||
paths.add "config/database", with: "config/database.yml"
|
||||
paths.add "config/secrets", with: "config/secrets.yml"
|
||||
paths.add "config/secrets", with: "config", glob: "secrets.yml{,.enc}"
|
||||
paths.add "config/environment", with: "config/environment.rb"
|
||||
paths.add "lib/templates"
|
||||
paths.add "log", with: "log/#{Rails.env}.log"
|
||||
|
|
|
@ -27,15 +27,22 @@ module Rails
|
|||
end
|
||||
|
||||
# Receives a namespace, arguments and the behavior to invoke the command.
|
||||
def invoke(namespace, args = [], **config)
|
||||
namespace = namespace.to_s
|
||||
namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace)
|
||||
namespace = "version" if %w( -v --version ).include? namespace
|
||||
def invoke(full_namespace, args = [], **config)
|
||||
namespace = full_namespace = full_namespace.to_s
|
||||
|
||||
if command = find_by_namespace(namespace)
|
||||
command.perform(namespace, args, config)
|
||||
if char = namespace =~ /:(\w+)$/
|
||||
command_name, namespace = $1, namespace.slice(0, char)
|
||||
else
|
||||
find_by_namespace("rake").perform(namespace, args, config)
|
||||
command_name = namespace
|
||||
end
|
||||
|
||||
command_name = "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name)
|
||||
namespace = "version" if %w( -v --version ).include?(command_name)
|
||||
|
||||
if command = find_by_namespace(namespace, command_name)
|
||||
command.perform(command_name, args, config)
|
||||
else
|
||||
find_by_namespace("rake").perform(full_namespace, args, config)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -52,8 +59,10 @@ module Rails
|
|||
#
|
||||
# Notice that "rails:commands:webrat" could be loaded as well, what
|
||||
# Rails looks for is the first and last parts of the namespace.
|
||||
def find_by_namespace(name) # :nodoc:
|
||||
lookups = [ name, "rails:#{name}" ]
|
||||
def find_by_namespace(namespace, command_name = nil) # :nodoc:
|
||||
lookups = [ namespace ]
|
||||
lookups << "#{namespace}:#{command_name}" if command_name
|
||||
lookups.concat lookups.map { |lookup| "rails:#{lookup}" }
|
||||
|
||||
lookup(lookups)
|
||||
|
||||
|
|
|
@ -56,7 +56,9 @@ module Rails
|
|||
end
|
||||
|
||||
def perform(command, args, config) # :nodoc:
|
||||
command = nil if Rails::Command::HELP_MAPPINGS.include?(args.first)
|
||||
if Rails::Command::HELP_MAPPINGS.include?(args.first)
|
||||
command, args = "help", []
|
||||
end
|
||||
|
||||
dispatch(command, args.dup, nil, config)
|
||||
end
|
||||
|
@ -111,7 +113,7 @@ module Rails
|
|||
# For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb`
|
||||
# would return `rails/test`.
|
||||
def default_command_root
|
||||
path = File.expand_path(File.join("../commands", command_name), __dir__)
|
||||
path = File.expand_path(File.join("../commands", command_root_namespace), __dir__)
|
||||
path if File.exist?(path)
|
||||
end
|
||||
|
||||
|
@ -129,6 +131,10 @@ module Rails
|
|||
super
|
||||
end
|
||||
end
|
||||
|
||||
def command_root_namespace
|
||||
(namespace.split(":") - %w( rails )).first
|
||||
end
|
||||
end
|
||||
|
||||
def help
|
||||
|
|
52
railties/lib/rails/commands/secrets/USAGE
Normal file
52
railties/lib/rails/commands/secrets/USAGE
Normal file
|
@ -0,0 +1,52 @@
|
|||
=== Storing Encrypted Secrets in Source Control
|
||||
|
||||
The Rails `secrets` commands helps encrypting secrets to slim a production
|
||||
environment's `ENV` hash. It's also useful for atomic deploys: no need to
|
||||
coordinate key changes to get everything working as the keys are shipped
|
||||
with the code.
|
||||
|
||||
=== Setup
|
||||
|
||||
Run `bin/rails secrets:setup` to opt in and generate the `config/secrets.yml.key`
|
||||
and `config/secrets.yml.enc` files.
|
||||
|
||||
The latter contains all the keys to be encrypted while the former holds the
|
||||
encryption key.
|
||||
|
||||
Don't lose the key! Put it in a password manager your team can access.
|
||||
Should you lose it no one, including you, will be able to access any encrypted
|
||||
secrets.
|
||||
Don't commit the key! Add `config/secrets.yml.key` to your source control's
|
||||
ignore file. If you use Git, Rails handles this for you.
|
||||
|
||||
Rails also looks for the key in `ENV["RAILS_MASTER_KEY"]` if that's easier to
|
||||
manage.
|
||||
|
||||
You could prepend that to your server's start command like this:
|
||||
|
||||
RAILS_MASTER_KEY="im-the-master-now-hahaha" server.start
|
||||
|
||||
|
||||
The `config/secrets.yml.enc` has much the same format as `config/secrets.yml`:
|
||||
|
||||
production:
|
||||
secret_key_base: so-secret-very-hidden-wow
|
||||
payment_processing_gateway_key: much-safe-very-gaedwey-wow
|
||||
|
||||
But that's where the similarities between `secrets.yml` and `secrets.yml.enc`
|
||||
end, e.g. no keys from `secrets.yml` will be moved to `secrets.yml.enc` and
|
||||
be encrypted.
|
||||
|
||||
A `shared:` top level key is also supported such that any keys there is merged
|
||||
into the other environments.
|
||||
|
||||
=== Editing Secrets
|
||||
|
||||
After `bin/rails secrets:setup`, run `bin/rails secrets:edit`.
|
||||
|
||||
That command opens a temporary file in `$EDITOR` with the decrypted contents of
|
||||
`config/secrets.yml.enc` to edit the encrypted secrets.
|
||||
|
||||
When the temporary file is next saved the contents are encrypted and written to
|
||||
`config/secrets.yml.enc` while the file itself is destroyed to prevent secrets
|
||||
from leaking.
|
50
railties/lib/rails/commands/secrets/secrets_command.rb
Normal file
50
railties/lib/rails/commands/secrets/secrets_command.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
require "active_support"
|
||||
require "rails/secrets"
|
||||
|
||||
module Rails
|
||||
module Command
|
||||
class SecretsCommand < Rails::Command::Base # :nodoc:
|
||||
def help
|
||||
say "Usage:\n #{self.class.banner}"
|
||||
say ""
|
||||
say self.class.desc
|
||||
end
|
||||
|
||||
def setup
|
||||
require "rails/generators"
|
||||
require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
|
||||
|
||||
Rails::Generators::EncryptedSecretsGenerator.start
|
||||
end
|
||||
|
||||
def edit
|
||||
require_application_and_environment!
|
||||
|
||||
Rails::Secrets.read_for_editing do |tmp_path|
|
||||
watch tmp_path do
|
||||
puts "Waiting for secrets file to be saved. Abort with Ctrl-C."
|
||||
system("\$EDITOR #{tmp_path}")
|
||||
end
|
||||
end
|
||||
|
||||
puts "New secrets encrypted and saved."
|
||||
rescue Interrupt
|
||||
puts "Aborted changing encrypted secrets: nothing saved."
|
||||
rescue Rails::Secrets::MissingKeyError => error
|
||||
say error.message
|
||||
end
|
||||
|
||||
private
|
||||
def watch(tmp_path)
|
||||
mtime, start_time = File.mtime(tmp_path), Time.now
|
||||
|
||||
yield
|
||||
|
||||
editor_exits_after_open = $?.success? && (Time.now - start_time) < 1
|
||||
if editor_exits_after_open
|
||||
sleep 0.250 until File.mtime(tmp_path) != mtime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -214,6 +214,7 @@ module Rails
|
|||
rails.map! { |n| n.sub(/^rails:/, "") }
|
||||
rails.delete("app")
|
||||
rails.delete("plugin")
|
||||
rails.delete("encrypted_secrets")
|
||||
|
||||
hidden_namespaces.each { |n| groups.delete(n.to_s) }
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ Rails.application.configure do
|
|||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Attempt to read encrypted secrets from `config/secrets.yml.enc`.
|
||||
# Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
|
||||
# `config/secrets.yml.key`.
|
||||
config.read_encrypted_secrets = true
|
||||
|
||||
# Disable serving static files from the `/public` folder by default since
|
||||
# Apache or NGINX already handles this.
|
||||
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
||||
|
|
|
@ -23,8 +23,10 @@ development:
|
|||
test:
|
||||
secret_key_base: <%= app_secret %>
|
||||
|
||||
# Do not keep production secrets in the repository,
|
||||
# instead read values from the environment.
|
||||
# Do not keep production secrets in the unencrypted secrets file.
|
||||
# Instead, either read values from the environment.
|
||||
# Or, use `bin/rails secrets:setup` to configure encrypted secrets
|
||||
# and move the `production:` environment over there.
|
||||
|
||||
production:
|
||||
secret_key_base: <%%= ENV["SECRET_KEY_BASE"] %>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
require "rails/generators/base"
|
||||
require "rails/secrets"
|
||||
|
||||
module Rails
|
||||
module Generators
|
||||
class EncryptedSecretsGenerator < Base
|
||||
def add_secrets_key_file
|
||||
unless File.exist?("config/secrets.yml.key") || File.exist?("config/secrets.yml.enc")
|
||||
key = Rails::Secrets.generate_key
|
||||
|
||||
say "Adding config/secrets.yml.key to store the encryption key: #{key}"
|
||||
say ""
|
||||
say "Save this in a password manager your team can access."
|
||||
say ""
|
||||
say "If you lose the key, no one, including you, can access any encrypted secrets."
|
||||
|
||||
say ""
|
||||
create_file "config/secrets.yml.key", key
|
||||
say ""
|
||||
end
|
||||
end
|
||||
|
||||
def ignore_key_file
|
||||
if File.exist?(".gitignore")
|
||||
unless File.read(".gitignore").include?(key_ignore)
|
||||
say "Ignoring config/secrets.yml.key so it won't end up in Git history:"
|
||||
say ""
|
||||
append_to_file ".gitignore", key_ignore
|
||||
say ""
|
||||
end
|
||||
else
|
||||
say "IMPORTANT: Don't commit config/secrets.yml.key. Add this to your ignore file:"
|
||||
say key_ignore, :on_green
|
||||
say ""
|
||||
end
|
||||
end
|
||||
|
||||
def add_encrypted_secrets_file
|
||||
unless File.exist?("config/secrets.yml.enc")
|
||||
say "Adding config/secrets.yml.enc to store secrets that needs to be encrypted."
|
||||
say ""
|
||||
|
||||
template "config/secrets.yml.enc" do |prefill|
|
||||
say ""
|
||||
say "For now the file contains this but it's been encrypted with the generated key:"
|
||||
say ""
|
||||
say prefill, :on_green
|
||||
say ""
|
||||
|
||||
Secrets.encrypt(prefill)
|
||||
end
|
||||
|
||||
say "You can edit encrypted secrets with `bin/rails secrets:edit`."
|
||||
|
||||
say "Add this to your config/environments/production.rb:"
|
||||
say "config.read_encrypted_secrets = true"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def key_ignore
|
||||
[ "", "# Ignore encrypted secrets key file.", "config/secrets.yml.key", "" ].join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
# See `secrets.yml` for tips on generating suitable keys.
|
||||
# production:
|
||||
# external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…
|
111
railties/lib/rails/secrets.rb
Normal file
111
railties/lib/rails/secrets.rb
Normal file
|
@ -0,0 +1,111 @@
|
|||
require "yaml"
|
||||
|
||||
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
|
||||
|
||||
@read_encrypted_secrets = false
|
||||
@root = File # Wonky, but ensures `join` uses the current directory.
|
||||
|
||||
class << self
|
||||
attr_writer :root
|
||||
attr_accessor :read_encrypted_secrets
|
||||
|
||||
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
|
||||
cipher = new_cipher
|
||||
SecureRandom.hex(cipher.key_len)[0, cipher.key_len]
|
||||
end
|
||||
|
||||
def key
|
||||
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
|
||||
end
|
||||
|
||||
def encrypt(text)
|
||||
cipher(:encrypt, text)
|
||||
end
|
||||
|
||||
def decrypt(data)
|
||||
cipher(:decrypt, 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
|
||||
tmp_path = File.join(Dir.tmpdir, File.basename(path))
|
||||
IO.binwrite(tmp_path, read)
|
||||
|
||||
yield tmp_path
|
||||
|
||||
write(IO.binread(tmp_path))
|
||||
ensure
|
||||
FileUtils.rm(tmp_path) if File.exist?(tmp_path)
|
||||
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")
|
||||
if @read_encrypted_secrets
|
||||
decrypt(IO.binread(path))
|
||||
else
|
||||
""
|
||||
end
|
||||
else
|
||||
IO.read(path)
|
||||
end
|
||||
end
|
||||
|
||||
def new_cipher
|
||||
OpenSSL::Cipher.new("aes-256-cbc")
|
||||
end
|
||||
|
||||
def cipher(mode, data)
|
||||
cipher = new_cipher.public_send(mode)
|
||||
cipher.key = key
|
||||
cipher.update(data) << cipher.final
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -335,6 +335,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
|
|||
end
|
||||
assert_file "config/environments/production.rb" do |content|
|
||||
assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
|
||||
assert_match(/^ config\.read_encrypted_secrets = true/, content)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
42
railties/test/generators/encrypted_secrets_generator_test.rb
Normal file
42
railties/test/generators/encrypted_secrets_generator_test.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
require "generators/generators_test_helper"
|
||||
require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
|
||||
|
||||
class EncryptedSecretsGeneratorTest < Rails::Generators::TestCase
|
||||
include GeneratorsTestHelper
|
||||
|
||||
def setup
|
||||
super
|
||||
cd destination_root
|
||||
end
|
||||
|
||||
def test_generates_key_file_and_encrypted_secrets_file
|
||||
run_generator
|
||||
|
||||
assert_file "config/secrets.yml.key", /[\w\d]+/
|
||||
|
||||
assert File.exist?("config/secrets.yml.enc")
|
||||
assert_no_match(/production:\n# external_api_key: [\w\d]+/, IO.binread("config/secrets.yml.enc"))
|
||||
assert_match(/production:\n# external_api_key: [\w\d]+/, Rails::Secrets.read)
|
||||
end
|
||||
|
||||
def test_appends_to_gitignore
|
||||
FileUtils.touch(".gitignore")
|
||||
|
||||
run_generator
|
||||
|
||||
assert_file ".gitignore", /config\/secrets.yml.key/, /(?!config\/secrets.yml.enc)/
|
||||
end
|
||||
|
||||
def test_warns_when_ignore_is_missing
|
||||
assert_match(/Add this to your ignore file/i, run_generator)
|
||||
end
|
||||
|
||||
def test_doesnt_generate_a_new_key_file_if_already_opted_in_to_encrypted_secrets
|
||||
FileUtils.mkdir("config")
|
||||
File.open("config/secrets.yml.enc", "w") { |f| f.puts "already secrety" }
|
||||
|
||||
run_generator
|
||||
|
||||
assert_no_file "config/secrets.yml.key"
|
||||
end
|
||||
end
|
|
@ -22,6 +22,7 @@ require "active_support/core_ext/object/blank"
|
|||
require "active_support/testing/isolation"
|
||||
require "active_support/core_ext/kernel/reporting"
|
||||
require "tmpdir"
|
||||
require "rails/secrets"
|
||||
|
||||
module TestHelpers
|
||||
module Paths
|
||||
|
|
108
railties/test/secrets_test.rb
Normal file
108
railties/test/secrets_test.rb
Normal file
|
@ -0,0 +1,108 @@
|
|||
require "abstract_unit"
|
||||
require "isolation/abstract_unit"
|
||||
require "rails/generators"
|
||||
require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
|
||||
require "rails/secrets"
|
||||
|
||||
class Rails::SecretsTest < ActiveSupport::TestCase
|
||||
include ActiveSupport::Testing::Isolation
|
||||
|
||||
def setup
|
||||
build_app
|
||||
|
||||
@old_read_encrypted_secrets, Rails::Secrets.read_encrypted_secrets =
|
||||
Rails::Secrets.read_encrypted_secrets, true
|
||||
end
|
||||
|
||||
def teardown
|
||||
Rails::Secrets.read_encrypted_secrets = @old_read_encrypted_secrets
|
||||
|
||||
teardown_app
|
||||
end
|
||||
|
||||
test "setting read to false skips parsing" do
|
||||
Rails::Secrets.read_encrypted_secrets = false
|
||||
|
||||
Dir.chdir(app_path) do
|
||||
assert_equal Hash.new, Rails::Secrets.parse(%w( config/secrets.yml.enc ), env: "production")
|
||||
end
|
||||
end
|
||||
|
||||
test "raises when reading secrets without a key" do
|
||||
run_secrets_generator do
|
||||
FileUtils.rm("config/secrets.yml.key")
|
||||
|
||||
assert_raises Rails::Secrets::MissingKeyError do
|
||||
Rails::Secrets.key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "reading with ENV variable" do
|
||||
run_secrets_generator do
|
||||
begin
|
||||
old_key = ENV["RAILS_MASTER_KEY"]
|
||||
ENV["RAILS_MASTER_KEY"] = IO.binread("config/secrets.yml.key").strip
|
||||
FileUtils.rm("config/secrets.yml.key")
|
||||
|
||||
assert_match "production:\n# external_api_key", Rails::Secrets.read
|
||||
ensure
|
||||
ENV["RAILS_MASTER_KEY"] = old_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "reading from key file" do
|
||||
run_secrets_generator do
|
||||
File.binwrite("config/secrets.yml.key", "How do I know you feel it?")
|
||||
|
||||
assert_equal "How do I know you feel it?", Rails::Secrets.key
|
||||
end
|
||||
end
|
||||
|
||||
test "editing" do
|
||||
run_secrets_generator do
|
||||
decrypted_path = nil
|
||||
|
||||
Rails::Secrets.read_for_editing do |tmp_path|
|
||||
decrypted_path = tmp_path
|
||||
|
||||
assert_match(/production:\n# external_api_key/, File.read(tmp_path))
|
||||
|
||||
File.write(tmp_path, "Empty streets, empty nights. The Downtown Lights.")
|
||||
end
|
||||
|
||||
assert_not File.exist?(decrypted_path)
|
||||
assert_equal "Empty streets, empty nights. The Downtown Lights.", Rails::Secrets.read
|
||||
end
|
||||
end
|
||||
|
||||
test "merging secrets with encrypted precedence" do
|
||||
run_secrets_generator do
|
||||
File.write("config/secrets.yml", <<-end_of_secrets)
|
||||
test:
|
||||
yeah_yeah: lets-go-walking-down-this-empty-street
|
||||
end_of_secrets
|
||||
|
||||
Rails::Secrets.write(<<-end_of_secrets)
|
||||
test:
|
||||
yeah_yeah: lets-walk-in-the-cool-evening-light
|
||||
end_of_secrets
|
||||
|
||||
Rails.application.config.root = app_path
|
||||
Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
|
||||
assert_equal "lets-walk-in-the-cool-evening-light", Rails.application.secrets.yeah_yeah
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def run_secrets_generator
|
||||
Dir.chdir(app_path) do
|
||||
capture(:stdout) do
|
||||
Rails::Generators::EncryptedSecretsGenerator.start
|
||||
end
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue