Skip to content

Commit 56e3bd4

Browse files
committed
Fix: Do not override config.credentials when invoking rails credentials commands
1 parent 119e8bc commit 56e3bd4

File tree

8 files changed

+310
-24
lines changed

8 files changed

+310
-24
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ In case `config/credentials/#{Rails.app_env}.key` does not exist, it falls back
8282
As with default Rails behaviours, if `ENV["RAILS_MASTER_KEY"]` is present, it takes precedence over
8383
`config/credentials/#{Rails.app_env}.key` and `config/master.key`.
8484

85+
As with default Rails behaviours, when invoking `$ rails credentials` commands, specific the `--environment` option
86+
instead of using `APP_ENV` and `RAILS_ENV`.
87+
88+
```console
89+
# APP_ENV and RAILS_ENV are ignored.
90+
$ APP_ENV=foo RAILS_ENV=bar bin/rails credentials:edit --environment qaz
91+
create config/credentials/qaz.key
92+
create config/credentials/qaz.yml.enc
93+
```
94+
8595
Learn more in the [Heroku](#heroku) section below.
8696

8797
### Console

lib/rails/app_env/credentials.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
1+
require_relative "error"
2+
13
module Rails
24
module AppEnv
35
module Credentials
6+
class AlreadyInitializedError < Rails::AppEnv::Error; end
7+
48
class << self
9+
attr_reader :original
10+
11+
def initialize!
12+
raise AlreadyInitializedError.new "Rails::AppEnv::Credentials has been already initialized." if @initialized
13+
@initialized = true
14+
15+
@original = Rails.application.config.credentials
16+
Rails.application.config.credentials = configuration
17+
18+
monkey_patch_rails_credentials_command!
19+
end
20+
21+
def configuration
22+
ActiveSupport::InheritableOptions.new(
23+
content_path: content_path,
24+
key_path: key_path
25+
)
26+
end
27+
28+
private
29+
530
def content_path
631
path = Rails.root.join("config/credentials/#{Rails.app_env}.yml.enc")
732
path = Rails.root.join("config/credentials.yml.enc") unless path.exist?
@@ -13,6 +38,10 @@ def key_path
1338
path = Rails.root.join("config/master.key") unless path.exist?
1439
path
1540
end
41+
42+
def monkey_patch_rails_credentials_command!
43+
require_relative "../rails_ext/credentials_command"
44+
end
1645
end
1746
end
1847
end

lib/rails/app_env/error.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Rails
2+
module AppEnv
3+
class Error < StandardError; end
4+
end
5+
end

lib/rails/app_env/railtie.rb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
module Rails
22
module AppEnv
33
class Railtie < Rails::Railtie
4-
config.before_configuration do
4+
initializer :load_helpers, before: :initialize_logger do
55
Rails.extend(Helpers)
66
end
77

8-
config.before_configuration do |app|
9-
app.config.credentials.content_path = Rails::AppEnv::Credentials.content_path
10-
app.config.credentials.key_path = Rails::AppEnv::Credentials.key_path
8+
initializer :set_credentials, before: :initialize_logger do
9+
Rails::AppEnv::Credentials.initialize!
1110
end
1211

1312
config.after_initialize do
14-
Rails::Info.property "Application environment" do
15-
Rails.app_env
16-
end
13+
Rails::Info.property "Application environment", Rails.app_env
1714
end
1815

1916
console do |app|
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Rails
2+
module Command
3+
class CredentialsCommand
4+
def config
5+
Rails::AppEnv::Credentials.original
6+
end
7+
end
8+
end
9+
end
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
require_relative "../test_helper"
2+
require_relative "file_helpers"
3+
4+
module Rails::AppEnv::FeaturesTest
5+
class CredentialsCommandTest < ActiveSupport::TestCase
6+
include FileHelpers
7+
8+
def teardown
9+
cleanup_credentials_files
10+
end
11+
12+
test "does not override when --environment is custom" do
13+
arg_env = "foo"
14+
15+
["foo", "production", "development", "test", nil].each do |app_env|
16+
["foo", "production", "development", "test", nil].each do |rails_env|
17+
cleanup_credentials_files
18+
19+
run_edit_command(app_env:, rails_env:, arg_env:)
20+
21+
refute_files %w[
22+
config/credentials.yml.enc
23+
config/master.key
24+
config/credentials/production.yml.enc
25+
config/credentials/production.key
26+
config/credentials/development.yml.enc
27+
config/credentials/development.key
28+
config/credentials/test.yml.enc
29+
config/credentials/test.key
30+
config/credentials/.yml.enc
31+
config/credentials/.key
32+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
33+
34+
assert_files %w[
35+
config/credentials/foo.yml.enc
36+
config/credentials/foo.key
37+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
38+
end
39+
end
40+
end
41+
42+
test "does not override when --environment is production" do
43+
arg_env = "production"
44+
45+
["foo", "production", "development", "test", nil].each do |app_env|
46+
["foo", "production", "development", "test", nil].each do |rails_env|
47+
cleanup_credentials_files
48+
49+
run_edit_command(app_env:, rails_env:, arg_env:)
50+
51+
refute_files %w[
52+
config/credentials.yml.enc
53+
config/master.key
54+
config/credentials/foo.yml.enc
55+
config/credentials/foo.key
56+
config/credentials/development.yml.enc
57+
config/credentials/development.key
58+
config/credentials/test.yml.enc
59+
config/credentials/test.key
60+
config/credentials/.yml.enc
61+
config/credentials/.key
62+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
63+
64+
assert_files %w[
65+
config/credentials/production.yml.enc
66+
config/credentials/production.key
67+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
68+
end
69+
end
70+
end
71+
72+
test "does not override when --environment is development" do
73+
arg_env = "development"
74+
75+
["foo", "production", "development", "test", nil].each do |app_env|
76+
["foo", "production", "development", "test", nil].each do |rails_env|
77+
cleanup_credentials_files
78+
79+
run_edit_command(app_env:, rails_env:, arg_env:)
80+
81+
refute_files %w[
82+
config/credentials.yml.enc
83+
config/master.key
84+
config/credentials/foo.yml.enc
85+
config/credentials/foo.key
86+
config/credentials/production.yml.enc
87+
config/credentials/production.key
88+
config/credentials/test.yml.enc
89+
config/credentials/test.key
90+
config/credentials/.yml.enc
91+
config/credentials/.key
92+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
93+
94+
assert_files %w[
95+
config/credentials/development.yml.enc
96+
config/credentials/development.key
97+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
98+
end
99+
end
100+
end
101+
102+
test "does not override when --environment is test" do
103+
arg_env = "test"
104+
105+
["foo", "production", "development", "test", nil].each do |app_env|
106+
["foo", "production", "development", "test", nil].each do |rails_env|
107+
cleanup_credentials_files
108+
109+
run_edit_command(app_env:, rails_env:, arg_env:)
110+
111+
refute_files %W[
112+
config/credentials.yml.enc
113+
config/master.key
114+
config/credentials/foo.yml.enc
115+
config/credentials/foo.key
116+
config/credentials/production.yml.enc
117+
config/credentials/production.key
118+
config/credentials/development.yml.enc
119+
config/credentials/development.key
120+
config/credentials/.yml.enc
121+
config/credentials/.key
122+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
123+
124+
assert_files %w[
125+
config/credentials/test.yml.enc
126+
config/credentials/test.key
127+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
128+
end
129+
end
130+
end
131+
132+
test "does not override when --environment are blank" do
133+
arg_env = nil
134+
135+
["foo", "production", "development", "test", nil].each do |app_env|
136+
["foo", "production", "development", "test", nil].each do |rails_env|
137+
cleanup_credentials_files
138+
139+
run_edit_command(app_env:, rails_env:, arg_env:)
140+
141+
refute_files %w[
142+
config/credentials/foo.yml.enc
143+
config/credentials/foo.key
144+
config/credentials/production.yml.enc
145+
config/credentials/production.key
146+
config/credentials/development.yml.enc
147+
config/credentials/development.key
148+
config/credentials/test.yml.enc
149+
config/credentials/test.key
150+
config/credentials/.yml.enc
151+
config/credentials/.key
152+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
153+
154+
assert_files %w[
155+
config/credentials.yml.enc
156+
config/master.key
157+
], "when APP_ENV is #{app_env.inspect} and RAILS_ENV is #{rails_env.inspect} and arg_env is #{arg_env.inspect}"
158+
end
159+
end
160+
end
161+
162+
private
163+
164+
def run_edit_command(arg_env: nil, app_env: nil, rails_env: nil)
165+
env = {"VISUAL" => "cat", "EDITOR" => "cat", "APP_ENV" => app_env, "RAILS_ENV" => rails_env}
166+
args = arg_env ? ["--environment", arg_env] : []
167+
168+
_, status = Open3.capture2(env, "bin/rails", "credentials:edit", *args, {chdir: DUMMY_ROOT})
169+
170+
assert_predicate status, :success?
171+
end
172+
173+
def cleanup_credentials_files
174+
FileUtils.remove_dir dummy_path("config/credentials"), true
175+
FileUtils.remove_file dummy_path("config/credentials.yml.enc"), true
176+
FileUtils.remove_file dummy_path("config/master.key"), true
177+
end
178+
end
179+
end

test/features/file_helpers.rb

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
module FileHelpers
22
private
33

4-
def with_file(path, &block)
5-
return block.call nil if path.nil?
4+
def with_file(relative, &block)
5+
return block.call nil if relative.nil?
66

7-
full_path = Rails.root.join path
7+
full_path = dummy_path(relative)
88

99
FileUtils.mkdir_p File.dirname(full_path)
1010
FileUtils.touch full_path
@@ -13,4 +13,28 @@ def with_file(path, &block)
1313
ensure
1414
FileUtils.rm_f full_path unless full_path.nil?
1515
end
16+
17+
def assert_files(relatives, message = "")
18+
relatives.each do |relative|
19+
assert_file relative, message
20+
end
21+
end
22+
23+
def refute_files(relatives, message = "")
24+
relatives.each do |relative|
25+
refute_file relative, message
26+
end
27+
end
28+
29+
def assert_file(relative, message = "")
30+
assert File.exist?(dummy_path(relative)), ["Expected file #{relative.inspect} to exist, but it does", message.strip].join(" ").strip
31+
end
32+
33+
def refute_file(relative, message = "")
34+
refute File.exist?(dummy_path(relative)), ["Expected file #{relative.inspect} to not exist, but it does", message.strip].join(" ").strip
35+
end
36+
37+
def dummy_path(relative)
38+
File.expand_path(relative, DUMMY_ROOT)
39+
end
1640
end

0 commit comments

Comments
 (0)