safe_credentials 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +97 -0
- data/Rakefile +1 -0
- data/bin/safe_credentials +5 -0
- data/lib/safe_credentials.rb +7 -0
- data/lib/safe_credentials/cli.rb +60 -0
- data/lib/safe_credentials/config.rb +61 -0
- data/lib/safe_credentials/engine.rb +7 -0
- data/lib/safe_credentials/hash_encryptor.rb +81 -0
- data/lib/safe_credentials/hash_query.rb +28 -0
- data/lib/safe_credentials/version.rb +3 -0
- data/safe_credentials.gemspec +28 -0
- data/spec/fixtures/config.yml +21 -0
- data/spec/fixtures/encrypted_config.yml +22 -0
- data/spec/lib/safe_credential/config_spec.rb +32 -0
- data/spec/lib/safe_credential/hash_encryptor_spec.rb +144 -0
- data/spec/lib/safe_credential/hash_query_spec.rb +45 -0
- data/spec/spec_helper.rb +1 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 54bec910e5e20a0e32bccd0d099a9e6626e76865
|
4
|
+
data.tar.gz: 93c2902537cc72b1677bc1db7a75b504c7be2fbe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec293af4e8ae3cbf286df92493aa0811c5003705542776327a67c7b9cf26d606a847b420aa3433bea548f4bfa854eda7c5be43f93514ff7b8ec59ed48782da87
|
7
|
+
data.tar.gz: 0075d799c2e3b6b6768bf363e8dca7c096e1fe2163968963241042e57ef2725b2302b0cd25cb4dff060fbc6c92c576d4b5fb073e209b4b916bd3434bb4245789
|
data/.gitignore
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
config/encrypted_config.yml
|
19
|
+
.DS_Store
|
20
|
+
config/config.yml
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Alberto F. Capel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Safe Credentials
|
2
|
+
|
3
|
+
Safe Credentials allows you to encrypt sensitive credentials so you can
|
4
|
+
store your configuration files in source control.
|
5
|
+
|
6
|
+
## Motivation
|
7
|
+
|
8
|
+
To store configuration files in source control is always a tricky issue. You shouldn't
|
9
|
+
store your credentials in clear text in source control, but often your team needs a subset
|
10
|
+
of those credentials to test and execute the project.
|
11
|
+
|
12
|
+
A usual approach is to create a configuration file (config.yml or similar) but don't push it
|
13
|
+
to source control. Instead, you also create a dummy example file (config.yml.example) with dummy
|
14
|
+
values. When someone needs to access the real credentials he or she has to ask the project owner
|
15
|
+
for them.
|
16
|
+
|
17
|
+
This solution is not ideal, especially when you need to add add or change some configuration
|
18
|
+
parameter.
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Install the gem
|
23
|
+
|
24
|
+
```shell
|
25
|
+
|
26
|
+
$ gem install safe_credentials
|
27
|
+
|
28
|
+
```
|
29
|
+
|
30
|
+
Run the provided executable:
|
31
|
+
|
32
|
+
```shell
|
33
|
+
|
34
|
+
$ safe_credentials encrypt
|
35
|
+
|
36
|
+
Encrypting file config/config.yml
|
37
|
+
Enter your password:
|
38
|
+
Result stored in config/encrypted_config.yml
|
39
|
+
Adding config/config.yml to .gitignore.
|
40
|
+
|
41
|
+
```
|
42
|
+
|
43
|
+
Later, when you need to decrypt the credentials
|
44
|
+
|
45
|
+
```shell
|
46
|
+
$ bin/safe_credentials decrypt
|
47
|
+
|
48
|
+
Decrypting file config/encrypted_config.yml
|
49
|
+
Enter your password:
|
50
|
+
Result stored in config/config.yml
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
## Options
|
55
|
+
|
56
|
+
Choose the path to the real config file and the encrypted one:
|
57
|
+
|
58
|
+
```shell
|
59
|
+
|
60
|
+
safe_credentials encrypt --from path/to/config.yml --to path/to/decrypted_config.yml
|
61
|
+
|
62
|
+
```
|
63
|
+
|
64
|
+
Also you can choose to encrypt only some configuration parameters:
|
65
|
+
|
66
|
+
```shell
|
67
|
+
|
68
|
+
# Encrypt database variables in all environments
|
69
|
+
|
70
|
+
safe_credentials encrypt --vars **.database.*
|
71
|
+
|
72
|
+
# Encrypt production variables
|
73
|
+
|
74
|
+
safe_credentials encrypt --vars producion
|
75
|
+
|
76
|
+
# Encrypt only password variables
|
77
|
+
|
78
|
+
safe_credentials encrypt --vars **password
|
79
|
+
|
80
|
+
```
|
81
|
+
|
82
|
+
## Credits
|
83
|
+
|
84
|
+
Original idea seen on [John Resig's blog](http://ejohn.org/blog/keeping-passwords-in-source-control/)
|
85
|
+
|
86
|
+
## TODO
|
87
|
+
|
88
|
+
* Capistrano integration. Upload config file to remote server and decrypt it there.
|
89
|
+
* Support other formats beside YAML, like TOML or JSON.
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
1. Fork it
|
94
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
95
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
96
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
97
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "thor"
|
2
|
+
require 'io/console'
|
3
|
+
|
4
|
+
class SafeCredentialsCLI < Thor
|
5
|
+
|
6
|
+
desc "encrypt", "Encrypt configuration file"
|
7
|
+
option :from, desc: 'source file to encrypt', default: 'config/config.yml'
|
8
|
+
option :to, desc: 'target file', default: 'config/encrypted_config.yml'
|
9
|
+
option :vars, desc: 'glob expression with the keys to encode', default: '*'
|
10
|
+
def encrypt
|
11
|
+
|
12
|
+
puts " Encrypting file #{options[:from]}"
|
13
|
+
print " Enter your password: "
|
14
|
+
password = STDIN.noecho(&:gets)
|
15
|
+
puts ''
|
16
|
+
|
17
|
+
config = SafeCredentials::Config.new(options[:from], password)
|
18
|
+
|
19
|
+
config.encrypt!(options[:vars])
|
20
|
+
config.save(options[:to])
|
21
|
+
|
22
|
+
puts " Result stored in #{options[:to]}"
|
23
|
+
add_to_gitignore(options[:from])
|
24
|
+
puts ""
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "decrypt", "Decrypt configuration file"
|
28
|
+
option :from, desc: 'source file to encrypt', default: 'config/encrypted_config.yml'
|
29
|
+
option :to, desc: 'target file', default: 'config/config.yml'
|
30
|
+
option :vars, desc: 'glob expression with the keys to decode', default: '**encrypted_*'
|
31
|
+
def decrypt
|
32
|
+
puts ""
|
33
|
+
puts " Decrypting file #{options[:from]}"
|
34
|
+
print " Enter your password: "
|
35
|
+
password = STDIN.noecho(&:gets)
|
36
|
+
puts ''
|
37
|
+
|
38
|
+
config = SafeCredentials::Config.load_encrypted(password, options[:from])
|
39
|
+
|
40
|
+
config.decrypt!(options[:vars])
|
41
|
+
config.save(options[:to])
|
42
|
+
|
43
|
+
puts " Result stored in #{options[:to]}"
|
44
|
+
add_to_gitignore(options[:to])
|
45
|
+
puts ""
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def add_to_gitignore(config_file)
|
52
|
+
return unless File.exist?('.gitignore') && !File.read('.gitignore').match(config_file)
|
53
|
+
|
54
|
+
puts " Adding #{config_file} to .gitignore."
|
55
|
+
|
56
|
+
File.open('.gitignore', 'a') do |f|
|
57
|
+
f << "#{config_file}\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'yaml'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module SafeCredentials
|
6
|
+
|
7
|
+
class Config
|
8
|
+
attr_reader :path, :config_file, :envs
|
9
|
+
|
10
|
+
def initialize(path = 'config/config.yml', password = nil)
|
11
|
+
@path = File.expand_path(path)
|
12
|
+
yaml_source = ERB.new(File.read(path)).result
|
13
|
+
|
14
|
+
@cipher = Gibberish::AES.new(password)
|
15
|
+
|
16
|
+
@attributes = YAML.load(yaml_source)
|
17
|
+
@encryptor = HashEncryptor.new(@attributes, @cipher)
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](env_name)
|
21
|
+
@attributes[env_name.to_s]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.load_encrypted(password, path = 'config/encrypted_config.yml')
|
25
|
+
config = new(path, password)
|
26
|
+
config.decrypt!
|
27
|
+
config
|
28
|
+
|
29
|
+
rescue OpenSSL::Cipher::CipherError
|
30
|
+
$stderr.puts "Wrong password!"
|
31
|
+
exit -1
|
32
|
+
end
|
33
|
+
|
34
|
+
def encrypt!(paths = '*')
|
35
|
+
@encryptor.encrypt_paths!(paths)
|
36
|
+
|
37
|
+
rescue OpenSSL::Cipher::CipherError
|
38
|
+
$stderr.puts "Wrong password!"
|
39
|
+
exit -1
|
40
|
+
end
|
41
|
+
|
42
|
+
def decrypt!(paths = '**encrypted_*')
|
43
|
+
@encryptor.decrypt_paths!(paths)
|
44
|
+
|
45
|
+
rescue OpenSSL::Cipher::CipherError
|
46
|
+
$stderr.puts "Wrong password!"
|
47
|
+
exit -1
|
48
|
+
end
|
49
|
+
|
50
|
+
def save(file_name = nil)
|
51
|
+
unless file_name
|
52
|
+
basename = File.basename(@path)
|
53
|
+
dirname = File.dirname(@path)
|
54
|
+
|
55
|
+
file_name = File.join(dirname, "encrypted_#{basename}")
|
56
|
+
end
|
57
|
+
|
58
|
+
File.write(file_name, @attributes.to_yaml)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SafeCredentials
|
2
|
+
class HashEncryptor
|
3
|
+
attr_reader :name, :attributes
|
4
|
+
|
5
|
+
def initialize(attributes, cipher)
|
6
|
+
@attributes, @cipher = attributes, cipher
|
7
|
+
@attributes.extend SafeCredentials::HashQuery
|
8
|
+
end
|
9
|
+
|
10
|
+
def encrypt_val(val)
|
11
|
+
case val
|
12
|
+
when String then @cipher.enc(val).strip
|
13
|
+
when Hash then encrypt_hash(val)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def encrypt_hash(h)
|
18
|
+
Hash.new.tap do |encrypted|
|
19
|
+
h.each do |k, v|
|
20
|
+
encrypted["encrypted_#{k}"] = encrypt_val(v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def decrypt_val(val)
|
26
|
+
case val
|
27
|
+
when String then @cipher.dec(val).strip
|
28
|
+
when Hash then decrypt_hash(val)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def decrypt_hash(h)
|
33
|
+
Hash.new.tap do |decrypted|
|
34
|
+
h.each do |k, v|
|
35
|
+
key = k.sub(/^encrypted_/, '')
|
36
|
+
decrypted[key] = decrypt_val(v)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def encrypt(path)
|
42
|
+
encrypt_val(value_for(path))
|
43
|
+
end
|
44
|
+
|
45
|
+
def encrypt!(path)
|
46
|
+
last_key, subhash = subhash_for(path)
|
47
|
+
new_key = "encrypted_#{last_key}"
|
48
|
+
|
49
|
+
subhash[new_key] = encrypt_val(subhash.delete(last_key))
|
50
|
+
end
|
51
|
+
|
52
|
+
def decrypt!(path)
|
53
|
+
last_key, subhash = subhash_for(path)
|
54
|
+
new_key = last_key.sub(/^encrypted_/,'')
|
55
|
+
|
56
|
+
subhash[new_key] = decrypt_val(subhash.delete(last_key))
|
57
|
+
end
|
58
|
+
|
59
|
+
def encrypt_paths!(query)
|
60
|
+
@attributes.query(query).each { |path| encrypt!(path) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def decrypt_paths!(query)
|
64
|
+
@attributes.query(query).each { |path| decrypt!(path) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def value_for(path)
|
68
|
+
parts = path.split('.')
|
69
|
+
parts.inject(attributes) { |attrs, key| attrs[key] }
|
70
|
+
end
|
71
|
+
|
72
|
+
def subhash_for(path)
|
73
|
+
parts = path.split('.')
|
74
|
+
last_key = parts.pop
|
75
|
+
|
76
|
+
subhash = parts.inject(attributes) { |attrs, key| attrs[key] }
|
77
|
+
|
78
|
+
[last_key, subhash]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module SafeCredentials
|
4
|
+
module HashQuery
|
5
|
+
|
6
|
+
def query(path, subhash = self, prefix = nil)
|
7
|
+
all_subpaths(path, subhash, prefix).find_all { |k| File.fnmatch?(path.gsub('.', '/'), k.gsub('.', '/')) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def all_subpaths(path = '*', subhash = self, prefix = nil)
|
11
|
+
parts = path.split('.')
|
12
|
+
first_part = parts.shift
|
13
|
+
|
14
|
+
matching_pathes = subhash.keys
|
15
|
+
|
16
|
+
subpaths = matching_pathes.collect do |subpath|
|
17
|
+
if Hash === subhash[subpath]
|
18
|
+
new_prefix = [prefix, subpath].compact.join('.')
|
19
|
+
all_subpaths(parts.join('.'), subhash[subpath], new_prefix)
|
20
|
+
else
|
21
|
+
[prefix, subpath].join('.')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Set.new(subpaths).flatten
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'safe_credentials/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "safe_credentials"
|
8
|
+
spec.version = SafeCredentials::VERSION
|
9
|
+
spec.authors = ["Alberto F. Capel"]
|
10
|
+
spec.email = ["afcapel@gmail.com"]
|
11
|
+
spec.description = %q{Encrypt sensitive credentials so you can store your configuration files in source control}
|
12
|
+
spec.summary = %q{Safe Credentials allows you to encrypt sensitive credentials so you can
|
13
|
+
store your configuration files in source control.}
|
14
|
+
spec.homepage = "https://github.com/afcapel/safe_credentials"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "thor"
|
23
|
+
spec.add_dependency "gibberish"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
development:
|
2
|
+
database:
|
3
|
+
adapter: sqlite3
|
4
|
+
db_user: scott
|
5
|
+
db_password: tiger
|
6
|
+
user: wadus
|
7
|
+
password: secret
|
8
|
+
test:
|
9
|
+
database:
|
10
|
+
adapter: sqlite3
|
11
|
+
db_user: scott
|
12
|
+
db_password: tiger
|
13
|
+
user: wadus
|
14
|
+
password: secret
|
15
|
+
production:
|
16
|
+
database:
|
17
|
+
adapter: postgresql
|
18
|
+
db_user: scott
|
19
|
+
db_password: tiger
|
20
|
+
user: wadus
|
21
|
+
password: secret
|
@@ -0,0 +1,22 @@
|
|
1
|
+
---
|
2
|
+
development:
|
3
|
+
database:
|
4
|
+
encrypted_adapter: U2FsdGVkX1/+7+ZLqxcnxDwe06l8CwS8HAY8cfFpeQY=
|
5
|
+
encrypted_db_user: U2FsdGVkX18VQzi5P3XfAAmoixK6bGzVsDNzAwl1Ajo=
|
6
|
+
encrypted_db_password: U2FsdGVkX196w5Ct9xZhzpVoTacPg70pQ2bUpL/Iqco=
|
7
|
+
encrypted_user: U2FsdGVkX19CORFfXnIxh7VTL7J5CWrpLdq2fAgKEuE=
|
8
|
+
encrypted_password: U2FsdGVkX1+xamsMYDDVMLOq5jSoS0JrQcNtL2IcsOA=
|
9
|
+
test:
|
10
|
+
database:
|
11
|
+
encrypted_adapter: U2FsdGVkX19w9GcGmV7Jh3U221nNw8WLjdbAkr3yiFA=
|
12
|
+
encrypted_db_user: U2FsdGVkX18YQWsQJ9GnaTyPK0aRwS42CbuhEutaEAs=
|
13
|
+
encrypted_db_password: U2FsdGVkX1/eBxmVhmf2RmmZzD2WMQ0Pe8eUQGamJRk=
|
14
|
+
encrypted_user: U2FsdGVkX18rXFzZCaRnKfdmjeNiTegSG9h43B2UO74=
|
15
|
+
encrypted_password: U2FsdGVkX19EFT/I71fdwONxfAkOBVok9tgF3OUf0kY=
|
16
|
+
production:
|
17
|
+
database:
|
18
|
+
encrypted_adapter: U2FsdGVkX1/R5d/ET8H9PjUH0jhnPEdfj/XuaORgfjQ=
|
19
|
+
encrypted_db_user: U2FsdGVkX184exEAZE7oTPkB2N0oDeskyGD52aJr3zI=
|
20
|
+
encrypted_db_password: U2FsdGVkX1/ikJtE7VHZlkNufe/6UGsO+T51mnqJuvE=
|
21
|
+
encrypted_user: U2FsdGVkX18IPO/BNBSg+FVLOAeuSS7hObLOqL6Z+nY=
|
22
|
+
encrypted_password: U2FsdGVkX18L5ZjBRvnt0deVWcmNa26ZkCKJ4ueNWmQ=
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SafeCredentials::Config do
|
4
|
+
|
5
|
+
let(:config_file) { File.expand_path('../../../fixtures/config.yml', __FILE__)}
|
6
|
+
let(:encrypted_config_file) { File.expand_path('../../../fixtures/encrypted_config.yml', __FILE__)}
|
7
|
+
|
8
|
+
let(:config) { SafeCredentials::Config.new(config_file, 'Super secret password') }
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
it "can save encrypted config file" do
|
14
|
+
orig_dev_env = deep_copy(config['development'])
|
15
|
+
orig_test_env = deep_copy(config['test'])
|
16
|
+
orig_prod_env = deep_copy(config['production'])
|
17
|
+
|
18
|
+
config.encrypt!
|
19
|
+
|
20
|
+
config.save
|
21
|
+
|
22
|
+
loaded_config = SafeCredentials::Config.load_encrypted('Super secret password', encrypted_config_file)
|
23
|
+
|
24
|
+
loaded_config['development'].should == orig_dev_env
|
25
|
+
loaded_config['production'].should == orig_prod_env
|
26
|
+
loaded_config['test'].should == orig_test_env
|
27
|
+
end
|
28
|
+
|
29
|
+
def deep_copy(hash)
|
30
|
+
Marshal.load(Marshal.dump(hash))
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SafeCredentials::HashEncryptor do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
cipher = Gibberish::AES.new('abracadabra')
|
7
|
+
|
8
|
+
@config = {
|
9
|
+
'development' => {
|
10
|
+
'user' => 'wadus',
|
11
|
+
'password' => 'secret',
|
12
|
+
'database' => {
|
13
|
+
'adapter' => 'sqlite',
|
14
|
+
'db_user' => 'scott',
|
15
|
+
'db_password' => 'tiger'
|
16
|
+
}
|
17
|
+
},
|
18
|
+
'production' => {
|
19
|
+
'user' => 'admin',
|
20
|
+
'password' => '1234',
|
21
|
+
'database' => {
|
22
|
+
'adapter' => 'postgresql',
|
23
|
+
'db_user' => 'scott',
|
24
|
+
'db_password' => 'tiger'
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
@hash_encryptor = SafeCredentials::HashEncryptor.new(@config, cipher)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can encrypt/decrypt a value" do
|
33
|
+
encrypted = @hash_encryptor.encrypt_val('testing')
|
34
|
+
encrypted.should_not == 'testing'
|
35
|
+
@hash_encryptor.decrypt_val(encrypted).should == 'testing'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "can encrypt/decrypt an env" do
|
39
|
+
encrypted_env = @hash_encryptor.encrypt('development')
|
40
|
+
|
41
|
+
encrypted_env['encrypted_user'].should_not be_nil
|
42
|
+
encrypted_env['encrypted_password'].should_not be_nil
|
43
|
+
|
44
|
+
encrypted_env['user'].should be_nil
|
45
|
+
encrypted_env['password'].should be_nil
|
46
|
+
|
47
|
+
decrypted_env = @hash_encryptor.decrypt_val(encrypted_env)
|
48
|
+
|
49
|
+
decrypted_env['user'].should == 'wadus'
|
50
|
+
decrypted_env['password'].should == 'secret'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can find the value for a nested path" do
|
54
|
+
@hash_encryptor.value_for('production.database').should == {
|
55
|
+
'adapter' => 'postgresql',
|
56
|
+
'db_user' => 'scott',
|
57
|
+
'db_password' => 'tiger'
|
58
|
+
}
|
59
|
+
|
60
|
+
@hash_encryptor.value_for('production.database.db_password').should == 'tiger'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "can encrypt/decrypt a nested path" do
|
64
|
+
encrypted_db = @hash_encryptor.encrypt('production.database')
|
65
|
+
|
66
|
+
encrypted_db['encrypted_db_user'].should_not be_nil
|
67
|
+
encrypted_db['encrypted_db_password'].should_not be_nil
|
68
|
+
|
69
|
+
encrypted_db['db_user'].should be_nil
|
70
|
+
encrypted_db['db_password'].should be_nil
|
71
|
+
|
72
|
+
decrypted_db = @hash_encryptor.decrypt_val(encrypted_db)
|
73
|
+
|
74
|
+
decrypted_db.should == {
|
75
|
+
'adapter' => 'postgresql',
|
76
|
+
'db_user' => 'scott',
|
77
|
+
'db_password' => 'tiger'
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
it "can encrypt/decrypt an attribute within a nested path" do
|
82
|
+
encrypted_password = @hash_encryptor.encrypt('production.database.db_password')
|
83
|
+
encrypted_password.should_not == 'tiger'
|
84
|
+
|
85
|
+
@hash_encryptor.decrypt_val(encrypted_password).should == 'tiger'
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'can replace attributes in the hash with encrypted values' do
|
89
|
+
original_development = @config['development'].dup
|
90
|
+
|
91
|
+
@hash_encryptor.encrypt!('development')
|
92
|
+
|
93
|
+
@config['encrypted_development']['encrypted_user'].should_not be_nil
|
94
|
+
@config['encrypted_development']['encrypted_password'].should_not be_nil
|
95
|
+
|
96
|
+
@config['encrypted_development']['user'].should be_nil
|
97
|
+
@config['encrypted_development']['password'].should be_nil
|
98
|
+
|
99
|
+
decrypted_env = @hash_encryptor.decrypt_val(@config['encrypted_development'])
|
100
|
+
|
101
|
+
decrypted_env.should == original_development
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'can deep replace attributes in the hash with encrypted values' do
|
105
|
+
@hash_encryptor.encrypt!('production')
|
106
|
+
|
107
|
+
@config['encrypted_production']['encrypted_user'].should_not be_nil
|
108
|
+
@config['encrypted_production']['encrypted_password'].should_not be_nil
|
109
|
+
@config['encrypted_production']['encrypted_database'].should_not be_nil
|
110
|
+
@config['encrypted_production']['encrypted_database']['encrypted_db_user'].should_not be_nil
|
111
|
+
@config['encrypted_production']['encrypted_database']['encrypted_db_password'].should_not be_nil
|
112
|
+
|
113
|
+
@config['production'].should be_nil
|
114
|
+
@config['encrypted_production']['database'].should be_nil
|
115
|
+
|
116
|
+
decrypted_env = @hash_encryptor.decrypt_val(@config['encrypted_production'])
|
117
|
+
|
118
|
+
decrypted_env.should == {
|
119
|
+
'user' => 'admin',
|
120
|
+
'password' => '1234',
|
121
|
+
'database' => {
|
122
|
+
'adapter' => 'postgresql',
|
123
|
+
'db_user' => 'scott',
|
124
|
+
'db_password' => 'tiger'
|
125
|
+
}
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'can match keys to replace against a glob expresion' do
|
130
|
+
@hash_encryptor.encrypt_paths!('*.database.db_*')
|
131
|
+
|
132
|
+
@config['development']['database']['adapter'].should == 'sqlite'
|
133
|
+
@config['development']['database']['user'].should be_nil
|
134
|
+
@config['development']['database']['password'].should be_nil
|
135
|
+
@config['development']['database']['encrypted_db_user'].should_not be_nil
|
136
|
+
@config['development']['database']['encrypted_db_password'].should_not be_nil
|
137
|
+
|
138
|
+
@config['production']['database']['adapter'].should == 'postgresql'
|
139
|
+
@config['production']['database']['user'].should be_nil
|
140
|
+
@config['production']['database']['password'].should be_nil
|
141
|
+
@config['production']['database']['encrypted_db_user'].should_not be_nil
|
142
|
+
@config['production']['database']['encrypted_db_password'].should_not be_nil
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SafeCredentials::HashQuery do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@hash = {
|
7
|
+
'development' => {
|
8
|
+
'user' => 'wadus',
|
9
|
+
'password' => 'secret',
|
10
|
+
'database' => {
|
11
|
+
'adapter' => 'sqlite',
|
12
|
+
'db_user' => 'scott',
|
13
|
+
'db_password' => 'tiger'
|
14
|
+
}
|
15
|
+
},
|
16
|
+
'production' => {
|
17
|
+
'user' => 'admin',
|
18
|
+
'password' => '1234',
|
19
|
+
'database' => {
|
20
|
+
'adapter' => 'postgresql',
|
21
|
+
'db_user' => 'scott',
|
22
|
+
'db_password' => 'tiger'
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
@hash.extend SafeCredentials::HashQuery
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can query paths without glob expressions" do
|
31
|
+
@hash.query('production.database.adapter').should == ['production.database.adapter']
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can query matching using glob expressions" do
|
35
|
+
@hash.query('*.database.db_*').should =~ [
|
36
|
+
'development.database.db_password', 'development.database.db_user',
|
37
|
+
'production.database.db_password', 'production.database.db_user'
|
38
|
+
]
|
39
|
+
|
40
|
+
@hash.query('**.*password').should =~ [
|
41
|
+
'development.password', 'development.database.db_password',
|
42
|
+
'production.password', 'production.database.db_password'
|
43
|
+
]
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative '../lib/safe_credentials'
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safe_credentials
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alberto F. Capel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: gibberish
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Encrypt sensitive credentials so you can store your configuration files
|
84
|
+
in source control
|
85
|
+
email:
|
86
|
+
- afcapel@gmail.com
|
87
|
+
executables:
|
88
|
+
- safe_credentials
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- .gitignore
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/safe_credentials
|
98
|
+
- lib/safe_credentials.rb
|
99
|
+
- lib/safe_credentials/cli.rb
|
100
|
+
- lib/safe_credentials/config.rb
|
101
|
+
- lib/safe_credentials/engine.rb
|
102
|
+
- lib/safe_credentials/hash_encryptor.rb
|
103
|
+
- lib/safe_credentials/hash_query.rb
|
104
|
+
- lib/safe_credentials/version.rb
|
105
|
+
- safe_credentials.gemspec
|
106
|
+
- spec/.DS_Store
|
107
|
+
- spec/fixtures/config.yml
|
108
|
+
- spec/fixtures/encrypted_config.yml
|
109
|
+
- spec/lib/.DS_Store
|
110
|
+
- spec/lib/safe_credential/config_spec.rb
|
111
|
+
- spec/lib/safe_credential/hash_encryptor_spec.rb
|
112
|
+
- spec/lib/safe_credential/hash_query_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
homepage: https://github.com/afcapel/safe_credentials
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.0.0
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Safe Credentials allows you to encrypt sensitive credentials so you can store
|
138
|
+
your configuration files in source control.
|
139
|
+
test_files:
|
140
|
+
- spec/.DS_Store
|
141
|
+
- spec/fixtures/config.yml
|
142
|
+
- spec/fixtures/encrypted_config.yml
|
143
|
+
- spec/lib/.DS_Store
|
144
|
+
- spec/lib/safe_credential/config_spec.rb
|
145
|
+
- spec/lib/safe_credential/hash_encryptor_spec.rb
|
146
|
+
- spec/lib/safe_credential/hash_query_spec.rb
|
147
|
+
- spec/spec_helper.rb
|