secure_yaml 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ .*
2
+ !.gitignore
3
+ !.rvmrc
4
+ *.log
5
+ *.iws
6
+ *.orig
7
+ *.iml
8
+ *.ipr
9
+ .idea
10
+ *.ids
11
+ *.gem
12
+ .bundle
13
+ Gemfile.lock
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.3-p194@secure_yaml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ ### Overview
2
+
3
+ The storage of sensitive information (such as usernames and passwords) within source control is commonly avoided due to security concerns, i.e. an untrusted person gaining access to your code, would have access to your production database password.
4
+
5
+ This library attempts to address this concern by allowing sensitive information to be stored in YAML files in an encrypted form. Inspired by [Jasypt](http://www.jasypt.org/encrypting-configuration.html).
6
+ <br />
7
+
8
+ ### Usage
9
+
10
+ <strong>1) Install the secure_yaml gem</strong>
11
+
12
+ ```
13
+ > gem install secure_yaml
14
+ ```
15
+ <br />
16
+
17
+ <strong>2) Encrypt your sensitive properties, and copy them into your YAML file</strong>
18
+
19
+ The gem provides a simple command line utility called ```encrypt_property_for_yaml``` that prints out the encrypted form of a plain text property.
20
+
21
+ ```
22
+ USAGE: encrypt_property_for_yaml <SECRET_KEY> <PROPERTY_VALUE_TO_ENCRYPT>
23
+ ```
24
+
25
+ For example:
26
+
27
+ ```
28
+ > encrypt_property_for_yaml abc12345678 jdbc:mysql://11.22.33.44:3306/prod
29
+ ENC(1BVzrT18dxWisIKUPkq9k0SB/aKcT70VawodxucI) <-- copy this value into your YAML file
30
+ ```
31
+
32
+ When completed, your YAML file might look something like the following:
33
+
34
+ ```
35
+ production:
36
+ db_adapter: mysql
37
+ db_url: ENC(1BVzrT18dxWisIKUPkq9k0SB/aKcT70VawodxucI)
38
+ db_username: ENC(4BEzrT18dxdisIKFPkw7k0SB/hKcT80VawodxuwT)
39
+ db_password: ENC(2BVzrQ79deWisIKUPkq9k8SB/aKcT74CawoExuiP)
40
+ pool: 5
41
+ timeout: 5000
42
+
43
+ ```
44
+
45
+ Note:
46
+ * your YAML file can consist of a combination of plain text and encrypted property values
47
+ * the encrypted properties can be positioned within the YAML at any nested depth.
48
+
49
+ <br />
50
+
51
+ <strong>3) Supply the secret key (used by the encryption process in step 2) as an environmental property to your running app</strong>
52
+
53
+ By default, the expected name of this environmental property is 'PROPERTIES_ENCRYPTION_PASSWORD', but can be overridden if required.
54
+
55
+ ```
56
+ export PROPERTIES_ENCRYPTION_PASSWORD=abc12345678; ruby app.rb
57
+ ```
58
+
59
+ <strong>*** The value of the secret key must NOT be submitted to source control. Knowing the secret key allows a person to easily decrypt your properties.</strong>
60
+ <br />
61
+
62
+ <strong>4) Load and use the decrypted version of your YAML file within your app</strong>
63
+
64
+ ```ruby
65
+ require 'secure_yaml'
66
+
67
+ decrypted_yaml = SecureYaml::load(File.open('database.yml'))
68
+
69
+ # Alternatively, to override the default secret key environmental property name:
70
+ decrypted_yaml = SecureYaml::load(File.open('database.yml'), 'NEW_SECRET_KEY_PROPERTY_NAME')
71
+ ```
72
+
73
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc "run specs"
7
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "secure_yaml/cli/property_encryption_application"
4
+
5
+ SecureYaml::PropertyEncryptionApplication.new.execute(ARGV)
@@ -0,0 +1,32 @@
1
+ require 'openssl'
2
+ require 'digest/sha2'
3
+ require 'base64'
4
+
5
+ module SecureYaml
6
+
7
+ class Cipher
8
+
9
+ def encrypt(secret_key, plain_data)
10
+ cipher = create_cipher(secret_key)
11
+ cipher.encrypt
12
+ Base64.strict_encode64(cipher.update(plain_data) + cipher.final)
13
+ end
14
+
15
+ def decrypt(secret_key, encrypted_data)
16
+ cipher = create_cipher(secret_key)
17
+ cipher.decrypt
18
+ cipher.update(Base64.strict_decode64(encrypted_data)) + cipher.final
19
+ end
20
+
21
+ private
22
+
23
+ def create_cipher(secret_key)
24
+ cipher = OpenSSL::Cipher.new("AES-256-CFB")
25
+ cipher.encrypt
26
+ cipher.key = Digest::SHA2.new(256).digest(secret_key)
27
+ cipher
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,19 @@
1
+ require "secure_yaml"
2
+
3
+ module SecureYaml
4
+
5
+ class PropertyEncryptionApplication
6
+
7
+ def execute(command_line_args)
8
+
9
+ raise "USAGE: encrypt_property_for_yaml <SECRET_KEY> <PROPERTY_VALUE_TO_ENCRYPT>" unless command_line_args.length == 2
10
+
11
+ secret_key = command_line_args[0]
12
+ plain_text = command_line_args[1]
13
+
14
+ puts "#{SecureYaml::ENCRYPTED_PROPERTY_WRAPPER_ID}(#{SecureYaml::Cipher.new.encrypt(secret_key, plain_text)})"
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,17 @@
1
+ require 'secure_yaml/yaml_decrypter'
2
+
3
+ module SecureYaml
4
+
5
+ class Loader
6
+
7
+ def initialize(secret_key)
8
+ @decrypter = YamlDecrypter.new(secret_key)
9
+ end
10
+
11
+ def load(yaml_file)
12
+ @decrypter.decrypt(YAML::load(yaml_file))
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,5 @@
1
+ module SecureYaml
2
+
3
+ VERSION = "1.0.0"
4
+
5
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+ require 'secure_yaml/cipher'
3
+
4
+ module SecureYaml
5
+
6
+ class YamlDecrypter
7
+
8
+ def initialize(secret_key, cipher = Cipher.new)
9
+ @cipher = cipher
10
+ @secret_key = secret_key
11
+ end
12
+
13
+ def decrypt(yaml)
14
+ case yaml
15
+ when Hash
16
+ yaml.each_with_object({}) {|(key, value), new_hash| new_hash[key] = decrypt(value)}
17
+ when String
18
+ yaml.gsub(/^#{ENCRYPTED_PROPERTY_WRAPPER_ID}\((.*)\)$/) {@cipher.decrypt(@secret_key, $1)}
19
+ else
20
+ yaml
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,20 @@
1
+ require 'secure_yaml/loader'
2
+
3
+ module SecureYaml
4
+
5
+ ENCRYPTED_PROPERTY_WRAPPER_ID = 'ENC'
6
+
7
+ DEFAULT_SECRET_KEY_PROP_NAME = 'PROPERTIES_ENCRYPTION_PASSWORD'
8
+
9
+ def self.load(yaml_file, secret_key_prop_name = DEFAULT_SECRET_KEY_PROP_NAME)
10
+ SecureYaml::Loader.new(secret_key(secret_key_prop_name)).load(yaml_file)
11
+ end
12
+
13
+ private
14
+
15
+ def self.secret_key(secret_key_prop_name)
16
+ secret_key = ENV[secret_key_prop_name]
17
+ raise "#{secret_key_prop_name} env property not found" if secret_key.nil?
18
+ end
19
+
20
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "secure_yaml/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "secure_yaml"
7
+ s.version = SecureYaml::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Huw Lewis"]
10
+ s.email = ["huwtlewis@gmail.com"]
11
+ s.homepage = "https://github.com/qmg-hlewis/secure_yaml"
12
+ s.summary = %q{encryption protection for sensitive yaml properties}
13
+ s.description = %q{encryption protection for sensitive yaml properties}
14
+
15
+ s.rubyforge_project = "secure_yaml"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency 'rspec', "~> 2.10"
22
+ s.add_development_dependency 'rspec-mocks', "~> 2.10"
23
+ s.add_development_dependency 'bundler', "~> 1.1"
24
+ s.add_development_dependency 'rake', "~> 0.9"
25
+
26
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Cipher' do
4
+
5
+ before(:each) do
6
+ @cipher = SecureYaml::Cipher.new
7
+ @secret_key = "abc12345678"
8
+ @plain_text = "some plain text to encrypt"
9
+ end
10
+
11
+ it 'should decrypt encrypted data' do
12
+ encrypted = @cipher.encrypt(@secret_key, @plain_text)
13
+
14
+ decrypted = @cipher.decrypt(@secret_key, encrypted)
15
+
16
+ encrypted.should_not == @plain_text
17
+ decrypted.should == @plain_text
18
+ end
19
+
20
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Property encryption command line interface' do
4
+
5
+ before(:each) do
6
+ @secret_key = 'secret key'
7
+ @plain_text = 'text to encrypt'
8
+ @encrypted_text = 'encrypted text'
9
+ end
10
+
11
+ it 'should print encrypted property value for given secret key and plain text' do
12
+ cipher = double(SecureYaml::Cipher)
13
+ cipher.stub(:encrypt).with(@secret_key, @plain_text).and_return(@encrypted_text)
14
+ SecureYaml::Cipher.stub(:new).and_return(cipher)
15
+
16
+ $stdout.should_receive(:puts).with("#{SecureYaml::ENCRYPTED_PROPERTY_WRAPPER_ID}(#{@encrypted_text})")
17
+
18
+ SecureYaml::PropertyEncryptionApplication.new.execute([@secret_key, @plain_text])
19
+ end
20
+
21
+ it 'should raise error unless secret key and plain text have been included as command line args' do
22
+ expect {SecureYaml::PropertyEncrypterApplication.new.execute([])}.to raise_error
23
+ expect {SecureYaml::PropertyEncrypterApplication.new.execute([@secret_key])}.to raise_error
24
+ end
25
+
26
+ it 'should raise error if too many comand line args' do
27
+ expect {SecureYaml::PropertyEncryptionApplication.new.execute([@secret_key, @plain_text, 'unexpected'])}.to raise_error
28
+ end
29
+
30
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Loader' do
4
+
5
+ before(:each) do
6
+ @encrypted_yaml = {prop: 'encrypted'}
7
+ @decrypted_yaml = {prop: 'decrytped'}
8
+ @decrypter = double(SecureYaml::YamlDecrypter)
9
+ SecureYaml::YamlDecrypter.stub(:new).and_return(@decrypter)
10
+ end
11
+
12
+ it 'should load decrypted yaml file' do
13
+ YAML.stub(:load).and_return(@encrypted_yaml)
14
+ @decrypter.stub(:decrypt).with(@encrypted_yaml).and_return(@decrypted_yaml)
15
+
16
+ yaml = SecureYaml::Loader.new('').load(double(File))
17
+
18
+ yaml.should == @decrypted_yaml
19
+ end
20
+
21
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Yaml decrypter' do
4
+
5
+ before(:each) do
6
+ @secret_key = 'abc12345678'
7
+ @cipher = double(SecureYaml::Cipher)
8
+ @decrypter = SecureYaml::YamlDecrypter.new(@secret_key, @cipher)
9
+ @decrypted_result = 'decrypted data'
10
+ @plain_text = 'some plain text'
11
+ end
12
+
13
+ it 'should decrypt only marked encrypted properties' do
14
+ encrypted_data = 'encrypted data'
15
+ @cipher.stub(:decrypt).with(@secret_key, encrypted_data).and_return(@decrypted_result)
16
+
17
+ data = @decrypter.decrypt({:encrypted_prop => "ENC(#{encrypted_data})", :plain_prop => @plain_text})
18
+
19
+ data.should == {:encrypted_prop => @decrypted_result, :plain_prop => @plain_text}
20
+ end
21
+
22
+ it 'should decrypt encrypted properties containing parentheses' do
23
+ encrypted_prop_with_param = 'encrypted )data'
24
+ @cipher.stub(:decrypt).with(@secret_key, encrypted_prop_with_param).and_return(@decrypted_result)
25
+
26
+ data = @decrypter.decrypt({:encrypted_prop => "ENC(#{encrypted_prop_with_param})"})
27
+
28
+ data.should == {:encrypted_prop => @decrypted_result}
29
+ end
30
+
31
+ it 'should ignore any encrypted properties in unexpected formats' do
32
+ unexpected_formats = {:unexpected_1 => 'ENC(text', :unexpected_2 => 'ENCtext)', :unexpected_3 => 'EN(text)'}
33
+
34
+ data = @decrypter.decrypt(unexpected_formats)
35
+
36
+ data.should == unexpected_formats
37
+ end
38
+
39
+ it 'should recursively encrypt nested properties' do
40
+ @cipher.stub(:decrypt).and_return(@decrypted_result)
41
+
42
+ data = @decrypter.decrypt({:parent_prop => {:nested_prop => 'ENC(text)', :parent_prop_2 => {:nested_prop_2 => 'ENC(text)'}}})
43
+
44
+ data.should == {:parent_prop => {:nested_prop => @decrypted_result, :parent_prop_2 => {:nested_prop_2 => @decrypted_result}}}
45
+ end
46
+
47
+ it 'should ignore any property of non-string type' do
48
+ numeric_prop = {:numeric => 1}
49
+
50
+ data = @decrypter.decrypt(numeric_prop)
51
+
52
+ data.should == numeric_prop
53
+ end
54
+
55
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'SecureYaml' do
4
+
5
+ before(:each) do
6
+ @yaml = {prop: 'test'}
7
+ loader = double(SecureYaml::Loader)
8
+ loader.stub(:load).and_return(@yaml)
9
+ SecureYaml::Loader.stub(:new).and_return(loader)
10
+ end
11
+
12
+ it 'should load decrypted yaml file' do
13
+ ENV[SecureYaml::DEFAULT_SECRET_KEY_PROP_NAME] = 'secret key'
14
+
15
+ yaml = SecureYaml::load(double(File))
16
+
17
+ yaml.should == @yaml
18
+ end
19
+
20
+ it 'should raise error on load if secret key env property not set' do
21
+ ENV[SecureYaml::DEFAULT_SECRET_KEY_PROP_NAME] = nil
22
+
23
+ expect {SecureYaml::load(double(File))}.to raise_error
24
+ end
25
+
26
+ it 'should allow use of custom secret key property name' do
27
+ custom_secret_key_prop_name = 'CUSTOMER_SECRET_KEY_PROP_NAME'
28
+ ENV[custom_secret_key_prop_name] = 'secret key'
29
+
30
+ yaml = SecureYaml::load(double(File), custom_secret_key_prop_name)
31
+
32
+ yaml.should == @yaml
33
+ end
34
+
35
+ end
@@ -0,0 +1,9 @@
1
+ require 'rspec'
2
+ require 'rspec/mocks'
3
+
4
+ require 'secure_yaml'
5
+ require 'secure_yaml/cli/property_encryption_application'
6
+
7
+ RSpec.configure do |conf|
8
+ conf.include RSpec::Mocks::ExampleMethods
9
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secure_yaml
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Huw Lewis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.10'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.10'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec-mocks
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.10'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.10'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.1'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.9'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.9'
78
+ description: encryption protection for sensitive yaml properties
79
+ email:
80
+ - huwtlewis@gmail.com
81
+ executables:
82
+ - encrypt_property_for_yaml
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - .rvmrc
88
+ - Gemfile
89
+ - README.md
90
+ - Rakefile
91
+ - bin/encrypt_property_for_yaml
92
+ - lib/secure_yaml.rb
93
+ - lib/secure_yaml/cipher.rb
94
+ - lib/secure_yaml/cli/property_encryption_application.rb
95
+ - lib/secure_yaml/loader.rb
96
+ - lib/secure_yaml/version.rb
97
+ - lib/secure_yaml/yaml_decrypter.rb
98
+ - secure_yaml.gemspec
99
+ - spec/secure_yaml/cipher_spec.rb
100
+ - spec/secure_yaml/cli/property_encryption_application_spec.rb
101
+ - spec/secure_yaml/loader_spec.rb
102
+ - spec/secure_yaml/yaml_decrypter_spec.rb
103
+ - spec/secure_yaml_spec.rb
104
+ - spec/spec_helper.rb
105
+ homepage: https://github.com/qmg-hlewis/secure_yaml
106
+ licenses: []
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project: secure_yaml
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: encryption protection for sensitive yaml properties
129
+ test_files: []