diff --git a/Gemfile.lock b/Gemfile.lock index cd5ce07..c1536a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - eyaml (0.4.0) + eyaml (0.4.2) rbnacl (~> 7.1) thor (~> 1.1) diff --git a/lib/eyaml/railtie.rb b/lib/eyaml/railtie.rb index 1b701ab..eede2a2 100644 --- a/lib/eyaml/railtie.rb +++ b/lib/eyaml/railtie.rb @@ -35,8 +35,9 @@ class ConflictError < StandardError # for a public/private key in the key directory (either $EJSON_KEYDIR, if set, or /opt/ejson/keys) cipherdata = YAML.load_file(file) secrets = EYAML.decrypt(cipherdata, private_key: ENV[PRIVATE_KEY_ENV_VAR]) + .except("_public_key") + secrets = EYAML::Util.with_deep_deundescored_keys(secrets) .deep_symbolize_keys - .except(:_public_key) break Rails.application.send(secrets_or_credentials).deep_merge!(secrets) end diff --git a/lib/eyaml/util.rb b/lib/eyaml/util.rb index 799716f..80ed5e8 100644 --- a/lib/eyaml/util.rb +++ b/lib/eyaml/util.rb @@ -6,6 +6,35 @@ class << self def pretty_yaml(some_hash) some_hash.to_yaml.delete_prefix("---\n") end + + # This will look for any keys that starts with an underscore and duplicates that key-value pair + # but without the starting underscore. + # So {_a: "abab"} will become {_a: "abab", a: "abab"} + # This so we can easilly access our unencrypted secrets without having to add an underscore + def with_deep_deundescored_keys(hash) + hash.each_with_object({}) do |pair, total| + key, value = pair + case value + when Hash + child_hash = with_deep_deundescored_keys(value) + + if key.start_with?("_") + raise KeyError, "De-underscored key '#{key[1..]}' already exists." if total.key?(key[1..]) + + total[key[1..]] = child_hash + end + + total[key] = child_hash + else + if key.start_with?("_") + raise KeyError, "De-underscored key '#{key[1..]}' already exists." if total.key?(key[1..]) + + total[key[1..]] = value + end + total[key] = value + end + end + end end end end diff --git a/spec/eyaml/railtie_spec.rb b/spec/eyaml/railtie_spec.rb index 7a67a5d..213bd78 100644 --- a/spec/eyaml/railtie_spec.rb +++ b/spec/eyaml/railtie_spec.rb @@ -74,18 +74,21 @@ remove_auth_files_that_dont_end_with(".eyaml") run_load_hooks expect(credentials).to(include(_extension: "eyaml")) + expect(credentials).to(include(extension: "eyaml")) end it "eyml" do remove_auth_files_that_dont_end_with(".eyml") run_load_hooks expect(credentials).to(include(_extension: "eyml")) + expect(credentials).to(include(extension: "eyml")) end it "ejson" do remove_auth_files_that_dont_end_with(".ejson") run_load_hooks expect(credentials).to(include(_extension: "ejson")) + expect(credentials).to(include(extension: "ejson")) end end @@ -99,6 +102,7 @@ remove_auth_files_that_dont_end_with(".eyaml") run_load_hooks expect(credentials).to(include(_extension: "eyaml")) + expect(credentials).to(include(extension: "eyaml")) end it "eyml" do @@ -106,12 +110,14 @@ run_load_hooks expect(credentials).to(include(_extension: "eyml")) + expect(credentials).to(include(extension: "eyml")) end it "ejson" do remove_auth_files_that_dont_end_with(".ejson") run_load_hooks expect(credentials).to(include(_extension: "ejson")) + expect(credentials).to(include(extension: "ejson")) end end @@ -185,18 +191,21 @@ remove_auth_files_that_dont_end_with(".eyaml") run_load_hooks expect(secrets).to(include(_extension: "eyaml")) + expect(secrets).to(include(extension: "eyaml")) end it "eyml" do remove_auth_files_that_dont_end_with(".eyml") run_load_hooks expect(secrets).to(include(_extension: "eyml")) + expect(secrets).to(include(extension: "eyml")) end it "ejson" do remove_auth_files_that_dont_end_with(".ejson") run_load_hooks expect(secrets).to(include(_extension: "ejson")) + expect(secrets).to(include(extension: "ejson")) end end @@ -210,6 +219,7 @@ remove_auth_files_that_dont_end_with(".eyaml") run_load_hooks expect(secrets).to(include(_extension: "eyaml")) + expect(secrets).to(include(extension: "eyaml")) end it "eyml" do @@ -217,12 +227,14 @@ run_load_hooks expect(secrets).to(include(_extension: "eyml")) + expect(secrets).to(include(extension: "eyml")) end it "ejson" do remove_auth_files_that_dont_end_with(".ejson") run_load_hooks expect(secrets).to(include(_extension: "ejson")) + expect(secrets).to(include(extension: "ejson")) end end diff --git a/spec/eyaml/util_spec.rb b/spec/eyaml/util_spec.rb index c636278..39af87d 100644 --- a/spec/eyaml/util_spec.rb +++ b/spec/eyaml/util_spec.rb @@ -4,7 +4,21 @@ describe ".pretty_yaml" do it "will return a hash as YAML without the three dash prefix" do yaml_without_prefix = File.read(fixtures_root.join("pretty.yml")) - expect(EYAML::Util.pretty_yaml({"a" => "1", "b" => "2"})).to eq(yaml_without_prefix) + expect(EYAML::Util.pretty_yaml({"a"=>"1", "b"=>"2", "_c"=>{"_d"=>"3"}})).to eq(yaml_without_prefix) + end + end + + describe ".with_deep_deundescored_keys" do + it "will return a hash with all undescored entries duplicated" do + yaml_without_prefix = YAML.load_file(fixtures_root.join("pretty.yml")) + + expect(EYAML::Util.with_deep_deundescored_keys(yaml_without_prefix)).to eq({"a"=>"1", "b"=>"2", "c"=>{"d"=>"3", "_d"=>"3"}, "_c"=>{"d"=>"3", "_d"=>"3"}}) + end + + it "will raise when a de-underscored key already exists" do + yaml_without_prefix = YAML.load_file(fixtures_root.join("pretty.yml")).merge!("_b" => "X") + + expect { EYAML::Util.with_deep_deundescored_keys(yaml_without_prefix) }.to raise_error(KeyError) end end end diff --git a/spec/fixtures/data.ejson b/spec/fixtures/data.ejson index 96fbb56..5d3798d 100644 --- a/spec/fixtures/data.ejson +++ b/spec/fixtures/data.ejson @@ -5,6 +5,7 @@ "_skip_me": "not_secret", "_extension": "ejson", "_dont_skip_me": { - "another_secret": "EJ[1:vr6e0PrO6xzH5N9c6Rs8ERt+DJXZeS0rZPDIZxMJWDg=:B1Iyfp3NBN/Kox9kQXWLV7F8BkNCckTA:MJh0KNGPimGadsUbQUZY21/nZFlFtw==]" + "another_secret": "EJ[1:vr6e0PrO6xzH5N9c6Rs8ERt+DJXZeS0rZPDIZxMJWDg=:B1Iyfp3NBN/Kox9kQXWLV7F8BkNCckTA:MJh0KNGPimGadsUbQUZY21/nZFlFtw==]", + "_underscored_secret": "not encrypted" } } diff --git a/spec/fixtures/data.eyaml b/spec/fixtures/data.eyaml index 5f27c77..e82032a 100644 --- a/spec/fixtures/data.eyaml +++ b/spec/fixtures/data.eyaml @@ -5,3 +5,4 @@ _skip_me: "not_secret" _extension: "eyaml" _dont_skip_me: another_secret: "ssshhh" + _underscored_secret: "not encrypted" diff --git a/spec/fixtures/data.eyml b/spec/fixtures/data.eyml index c1e418a..2dc35fc 100644 --- a/spec/fixtures/data.eyml +++ b/spec/fixtures/data.eyml @@ -5,3 +5,4 @@ _skip_me: "not_secret" _extension: "eyml" _dont_skip_me: another_secret: "ssshhh" + _underscored_secret: "not encrypted" diff --git a/spec/fixtures/pretty.yml b/spec/fixtures/pretty.yml index 9c0eb5b..56ff992 100644 --- a/spec/fixtures/pretty.yml +++ b/spec/fixtures/pretty.yml @@ -1,2 +1,4 @@ a: '1' b: '2' +_c: + _d: '3' diff --git a/spec/support/encryption_helper.rb b/spec/support/encryption_helper.rb index 896330c..285fdcb 100644 --- a/spec/support/encryption_helper.rb +++ b/spec/support/encryption_helper.rb @@ -19,7 +19,8 @@ module EncryptionHelper "_skip_me" => "not_secret", "_extension" => "ejson", # This is only the correct value for data.ejson "_dont_skip_me" => { - "another_secret" => "ssshhh" + "another_secret" => "ssshhh", + "_underscored_secret" => "not encrypted" } } }