hiera-crypt 0.1 → 0.2

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.
data/bin/hiera-crypt CHANGED
@@ -1,3 +1,53 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env ruby
2
2
 
3
- exec gpg --symmetric --cipher-algo AES256 "$@"
3
+ require 'optparse'
4
+ require_relative '../lib/passwordbox'
5
+
6
+ password = nil
7
+ input = '-'
8
+ output = '-'
9
+ mode = :encrypt
10
+
11
+ OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{$0} [options] [FILE]"
13
+ opts.on('-f', '--password-file [FILE]', 'Read password from a file') do |f|
14
+ file_name = File.expand_path(f)
15
+ password = File.open(file_name, 'r').read.chomp
16
+ end
17
+ opts.on('-d', '--decrypt', 'Decrypt instead of encrypting') do |decrypt|
18
+ mode = :decrypt
19
+ end
20
+ opts.on('-o', '--output [FILE]', 'Write result to file instead of stdout') do |f|
21
+ output = f
22
+ end
23
+ end.parse!
24
+
25
+ input = ARGV.first if ARGV.length > 0
26
+
27
+ def read_password(prompt="Password: ")
28
+ `stty -echo`
29
+ STDERR.write(prompt)
30
+ STDERR.flush
31
+ STDIN.readline.chomp
32
+ rescue Interrupt
33
+ exit 1
34
+ ensure
35
+ STDERR.write("\n")
36
+ STDERR.flush
37
+ `stty echo`
38
+ end
39
+
40
+ password = read_password if password.nil?
41
+
42
+ crypto = PasswordBox.new(password)
43
+
44
+ in_file = input == '-' ? STDIN : File.open(input, 'r')
45
+
46
+ if mode == :encrypt
47
+ out = crypto.box(in_file.read, :base64)
48
+ elsif mode == :decrypt
49
+ out = crypto.open(in_file.read, :base64)
50
+ end
51
+
52
+ out_file = output == '-' ? STDOUT : File.open(output, 'w')
53
+ out_file.write(out)
data/hiera-crypt.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "hiera-crypt"
7
- spec.version = "0.1"
7
+ spec.version = "0.2"
8
8
  spec.authors = ["Carl Jackson"]
9
9
  spec.email = ["carl@avtok.com"]
10
10
  spec.description = "Encrypted file backend for Hiera"
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "hiera", "~> 1.2.1"
22
- spec.add_dependency "gpgme", "~> 2.0.2"
22
+ spec.add_dependency "pbkdf2", "~> 0.1.0"
23
+ spec.add_dependency "rbnacl", "~> 1.1.0"
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.3"
25
26
  spec.add_development_dependency "rake"
@@ -5,13 +5,11 @@ class Hiera
5
5
  class Crypt_backend
6
6
  DEBUG_PREFIX = '[crypt backend]'
7
7
 
8
- def initialize()
8
+ def initialize(cache=nil)
9
9
  unless Hiera::Config.include?(:crypt)
10
10
  raise "Expected :crypt section in hiera.yaml"
11
11
  end
12
12
  conf = Hiera::Config[:crypt]
13
- unless conf.include?(:password) || conf.include?(:password_file)
14
- end
15
13
  password = if conf.include?(:password)
16
14
  conf[:password]
17
15
  elsif conf.include?(:password_file)
@@ -22,10 +20,10 @@ class Hiera
22
20
  raise "Expected either a :password or :password_file"
23
21
  end
24
22
 
25
- @cache = {}
23
+ @cache = cache || Filecache.new
26
24
 
27
- require 'gpgme'
28
- @crypto = GPGME::Crypto.new(:password => password)
25
+ require 'passwordbox'
26
+ @crypto = PasswordBox.new(password)
29
27
  debug("Loaded!")
30
28
  end
31
29
 
@@ -40,11 +38,13 @@ class Hiera
40
38
  Backend.datasources(scope, order_override) do |source|
41
39
  debug("Looking for data source #{source}")
42
40
 
43
- file = File.join(Backend.datadir(:crypt, scope), source, "#{key}.gpg")
41
+ file = File.join(Backend.datadir(:crypt, scope), source, "#{key}.crypt")
44
42
  debug("Examining file #{file}")
45
43
  next unless File.exist?(file)
46
44
 
47
- plaintext = decrypt(file)
45
+ plaintext = @cache.read(file, String) do |data|
46
+ @crypto.open(data, :base64)
47
+ end
48
48
 
49
49
  return plaintext if resolution_type == :priority
50
50
 
@@ -55,21 +55,6 @@ class Hiera
55
55
  end
56
56
 
57
57
  private
58
- def decrypt(file)
59
- stat = File.stat(f = File.new(file))
60
- info = {:inode => stat.ino, :mtime => stat.mtime, :size => stat.size}
61
- @cache.delete(file) if @cache[file] && @cache[file][:info] != info
62
-
63
- debug("Using cached value for #{file}") if @cache.include?(file)
64
-
65
- @cache[file] ||= {
66
- :contents => @crypto.decrypt(f).to_s,
67
- :info => info
68
- }
69
-
70
- @cache[file][:contents]
71
- end
72
-
73
58
  def debug(msg)
74
59
  Hiera.debug("#{DEBUG_PREFIX} #{msg}")
75
60
  end
@@ -0,0 +1,70 @@
1
+ require 'rbnacl'
2
+ require 'pbkdf2'
3
+ require 'forwardable'
4
+
5
+ # A SecretBox that (like RandomNonceBox) automatically generates a suitable
6
+ # nonce, but also which uses PBKDF2 to derive a password of the right length.
7
+ class PasswordBox < Crypto::SecretBox
8
+ DEFAULT_PBKDF2_ITERS = 5000
9
+
10
+ # Create a new PasswordBox
11
+ #
12
+ # @param password [String] A password of any length
13
+ def initialize(password)
14
+ @password = password
15
+ end
16
+
17
+ # Encrypts the message using a salted key derived from the given password, and
18
+ # a random nonce.
19
+ #
20
+ # @param message [String] The message to encrypt
21
+ # @param encoding [Symbol] Encoding for the returned ciphertext
22
+ #
23
+ # @return [String] The encrypted message
24
+ def box(message, encoding = :raw)
25
+ nonce = generate_nonce
26
+ salt, iters, @key = generate_key
27
+ ciphertext = super(nonce, message)
28
+ Crypto::Encoder[encoding].encode(nonce + salt + iters + ciphertext)
29
+ end
30
+ alias encrypt box
31
+
32
+ # Decrypts the message. Extracts both the encryption nonce and the salt from
33
+ # the message.
34
+ #
35
+ # @param enciphered_message [String] The message to decrypt
36
+ # @param encoding [Symbol] Encoding for the given ciphertext
37
+ #
38
+ # @raise [CryptoError] If the message has been tampered with.
39
+ #
40
+ # @return [String] The plaintext of the message
41
+ def open(enciphered_message, encoding = :raw)
42
+ decoded = Crypto::Encoder[encoding].decode(enciphered_message)
43
+ nonce, salt, iters, ciphertext = extract(decoded)
44
+ @key = generate_key(salt, iters).last
45
+ super(nonce, ciphertext)
46
+ end
47
+ alias decrypt open
48
+
49
+ private
50
+ def generate_nonce
51
+ Crypto::Random.random_bytes(nonce_bytes)
52
+ end
53
+ def generate_key(salt=nil, iters=DEFAULT_PBKDF2_ITERS)
54
+ salt ||= generate_nonce
55
+ key = PBKDF2.new(
56
+ :password => @password,
57
+ :salt => salt,
58
+ :iterations => iters,
59
+ :hash_function => :sha256,
60
+ :key_length => key_bytes
61
+ )
62
+ [salt, [iters].pack("N"), key.bin_string]
63
+ end
64
+ def extract(bytes)
65
+ nonce = bytes.slice!(0, nonce_bytes)
66
+ salt = bytes.slice!(0, nonce_bytes)
67
+ iters = bytes.slice!(0, 4).unpack("N").first
68
+ [nonce, salt, iters, bytes]
69
+ end
70
+ end
@@ -0,0 +1 @@
1
+ Eu5V2p42KnCo2kkWH4VPmXxdpkBUussPJQwX5WzW4sMD/OEIzTjO9GadWin2ZPc/AAATiJoMMuEM1xFefXzu9SdUyfpr0TAhsm4AjV6AysFt7WJifs85wLOl1AI1SDihZKdh9/i9EuQ6odCpmI4O2MTIA61aw+g/v0eAyF3Sz7eWRALpGNz1PF1lNs0xlilDMq1P+Yk9/QtdESzJe+VrnW4TvsV1dEemSa91ctzhonnaxy06zgbljKMEE8UD+j8UUqwHigOzyRvk+iA4tvnnYcR10jJD1KJZYkLvQr8i01MdYXV8OHQNutnoqxQrzAM9H+vi3p3BMDdsvXPswyO0Ym3bdfeXiTno5kMWL/SIw+Y7kmsT9fN5uU2935POO/lBkESZ2pw0AsJSw/P4C2SF5lEpPaSzHVDzMPru1+bg+vUYufDqgLhT1TDRkDs0maIXuS4MmH/PqpTL3/5JbsUT/CLICEdSrWSCclvNw8mA21+Nqx7e9smEc2gPK05C9vGx6o27/+7IsdxTsUmp6SaWIwaPQFbEfRZARScZfouCq6swost9VFw1TnCYZHcf0EfdqVqdGoMP3UcxboQBrKKM0Y3qC/Z2O1pKlpFFAVYj6ZFM9D/hnHRVI+2wB3dTx2FtGpEoBXg2mYK33uOI5uazmSy3Vz9q8a24EgTF5dwduJ2zlXmm6yidu96MxL3w0k3BuB4wbAH5cCW/E+Kkj7T/9c39tcIfKekrgPejna9ONnkWRnkfF2Abh/YiCa5F6omWVc+dBBxpWRyFjLj4/ywkcqi+O7gU40JYIyb8A32z8EIfHk/FeH0zTBbjctBxu9R3zWRxkWy5JzkgHYGuS1oB3IZ8Ki7YZVHDTWmk3Lrm5cP6k2G1SKkLD5G+CuUpSREiC0CSFqmnRTM7WCqYojlB72QZbT8qQffidXjj1dzx2u9Puui0pz4bUOnNNDUaoNbVigJn+H+HpLxrx5CBFByaN/jdKsavayPVxgiSQe0=
@@ -0,0 +1 @@
1
+ MTSajTxKc8nGJ+ZjwEM7uaCoOp8ZSIesT4HhRmcn29FWoyMioPrsz+aylaHdyCxwAAATiOuxY8cDs9uHEZmxmxnX4uMjNFhlQ2j2b9InSonhKeb6MAgE3uUH3bcCkwJ87tjlKeqjzAihD3cnFzcCU8V66gFQsUFa0uTM3olAgYvCe/tvWx4VEe4eO4/igBrcaBQ3JevA06TzRZPV9Ge2PEjr8EocZB2BcWCtjjzeOD/Plk60QaC59nlW3s1T6ZFyFMH/ufmv1MEqXH2dK7RKECRrbCsnp4DGO2SQMMfJoVF3pcGObtalJbf8Q1vM0RVQYZD4q0Yg2EjrXf2vqN12ufnzY9p8uP2P+efwD0xzCCk3BrYd7GbRXwnVbARC3e7mRoVlJfM9GUot1kqZb7rNdBFSD/RVTSoCbUjdqieN8E510SHJzQ121gGm/OVzHIorBZ5FoBgEBrQDzAq9gpNuKEj5c6LQHaXw37ix5DxkMpM3LOnzWb3OALNb2vx28BdIo3Q+uBg/hVDhfDJygDiZJqSHYcamegZLsquqpSyT65x/BDazkqkNKIl33jSnbNWpAyB5kkl+KpnoY0bBWvIdbgoqe4iolJPOEqQlzUeXUzUhh28vn9N/F8tIt7xQPhIHwsa5hmp6zJy7YZ0V2PNfbRcvfmeO4iYHZVGb9wmz9Pwe7DoC8ub80YCz9092NIya/Y3WcMJRF0mZCCe44elT1NgBe6wx9fz52gh9d/dmQPs73QrSoS5bahYh3BucqVs0xsp1YJ4VqGtf0oNpwNaEGVp0bwtFHq0Tdtg2OegGPiNUMiVjTMVLUqb5C6f8NwLDG4JwtqZQAUMYODgWRI1UTNRSEKQcLpzrG1OEwf3MucYOVPed82xmlekGgF5GXBZG/9qbuwBHGBjfYbf30+OUqV2PEIWxPQSN4p0GfPp1pFa/m4cM7WrxYROey3bfcLnvk//qMjE4tHxmaldJd4y1qwrr6CGTzl/UAuMfCss=
@@ -0,0 +1 @@
1
+ L531Q4wbmLbrjSHbvUKBs+3cGB+E6lYeTMVCLgKXoMW//E2+6vfjycj+62hKYg6wAAATiIY3gSySt20vPOZxypNd5QIYloZ1JisvMlxE1nLUT6rk9SMF+eW/CFl6IskduAVCh/+H4thRK3uFewGkFUvbRTzcyXcPpKPaezm+SfGDI5gyU0CNv2dAeSZGAQl9YzNiiTkUucbmu4lg5w4mXR4cM5+WdKh0Z9bpt4oBJvXz7RCez35IBQYCZv/iO/YjZ1XZTe4jbfJNWdXzKkQhXj8aANn4kGEUZeUB2L7HMGPMnDEDjCtG/yzgpHhrusM+uiXYfI65+Q5G2zWGUEEwmBkmAKGMeAfgP3MBFUNTH58yxxmZmcxRpE35vqIpGGlsh8QDJwNDr02jhKn1Ebopo77nPy3LXB1oIbAq1bEknTcMWJ9v4yvVwD8knxwKv4b0R4PpoA/vnAtMQGS8jjgS8JkTrh0mxrmHVI4aqXad3UFQ6VEt3OTxlewLYxSxPdv2g5IQhQLCt21nOtDeU/SGAHCsSW4E5zqAVH5sZbekYYES70Z4PiRXqRWYfNhIiiv+asH+Ed8ikvTpqHpvCS9vccADJG1YmN/m55CGt9nCkvMMTMSHrAE8huCAMWaMgOshJ2F5NNwnylxpcLrLsraNTLGP4YyRJYQGsBZdL8SdrR4HteP3yMYUY3KHp+0yCSjGXWMso5QNJVnTX/VAi4yoQh07mocJOOdS0ah93nsv2Z4zmYTeOHzoBaviG2ID6/kD/UHlNawCbb1su1vSg+tfp7EEzF+BZU06LyWXlgZ8Eux1MkmwN/sU8t40rxew5cVIhcE6gxxCyQenumWhCRJJuAT0ukZ0wJjVOdX6ApkDoVRX6t+ZSuLR5/egLIzu0pLdpJFOidSas8j+6Mib89+KWU1ZsNOJ/cyGtO095QEumX+meWIKqKLFBzz8abOLQc7Ces4nOH/EusjmgsJgjawgI997unuMTRhtYpFmoTSSt7Kt54483bIWLK93xYBVspLQAQHou94TvlNnbifsj25/eobwx0nNWbq8lsjbtEhmz3q+dR3s6p0j3tzP61uLaSqqSEDlkYmI2DIa8NsoivOJB6/YsVOCdiMDiXlZc5hSW5OpDl8E7BionNT0MYSvk2uMEJGSXoZYPKfDXsNj0OamZaCI86XKf+9PRKyfvorP0R17KudU0eE19Ac7EfyBrYiDvz5E6wGHg/2Ba6vUM6wudEEf8vPmO0eA5d8AUfy35R43nwo0tpaWoCO2hykVCbm9lbLc2cmtWFv1xY9kttv3qbLc2kPPzIUVL8MoVPaD5fzr+Rr7RUrAGFdANjKzeM5xnjKy64lCGAAKFo0Xs9EBExESoiTBR5RjcaOOpBGhL1+IpQRs5+U2EqUj9WrBZ0DApDlfGB8ygbRxdIJYlnWaUqvAHz7axCiwsFVu9V/UCAjwQ5Q9CHHXVXXXmHB3LaG7Egrr0Xx0hms4Md7tksynHOKDOFb1IjRAmbuyJStA187J/DhCdeINFULMmMn606b6ouO5lCVcphb5WTg7kOZpIaXnYRDCpT4I8PjIHXT/5RgawMQ=
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiera-crypt
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Carl Jackson
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-07-14 00:00:00.000000000 Z
12
+ date: 2013-08-11 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: hiera
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ~>
18
20
  - !ruby/object:Gem::Version
@@ -20,27 +22,47 @@ dependencies:
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ~>
25
28
  - !ruby/object:Gem::Version
26
29
  version: 1.2.1
27
30
  - !ruby/object:Gem::Dependency
28
- name: gpgme
31
+ name: pbkdf2
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ~>
32
36
  - !ruby/object:Gem::Version
33
- version: 2.0.2
37
+ version: 0.1.0
34
38
  type: :runtime
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ~>
39
44
  - !ruby/object:Gem::Version
40
- version: 2.0.2
45
+ version: 0.1.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rbnacl
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.0
54
+ type: :runtime
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.0
41
62
  - !ruby/object:Gem::Dependency
42
63
  name: bundler
43
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
44
66
  requirements:
45
67
  - - ~>
46
68
  - !ruby/object:Gem::Version
@@ -48,6 +70,7 @@ dependencies:
48
70
  type: :development
49
71
  prerelease: false
50
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
51
74
  requirements:
52
75
  - - ~>
53
76
  - !ruby/object:Gem::Version
@@ -55,15 +78,17 @@ dependencies:
55
78
  - !ruby/object:Gem::Dependency
56
79
  name: rake
57
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
58
82
  requirements:
59
- - - '>='
83
+ - - ! '>='
60
84
  - !ruby/object:Gem::Version
61
85
  version: '0'
62
86
  type: :development
63
87
  prerelease: false
64
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
65
90
  requirements:
66
- - - '>='
91
+ - - ! '>='
67
92
  - !ruby/object:Gem::Version
68
93
  version: '0'
69
94
  description: Encrypted file backend for Hiera
@@ -82,11 +107,12 @@ files:
82
107
  - bin/hiera-crypt
83
108
  - hiera-crypt.gemspec
84
109
  - lib/hiera/backend/crypt_backend.rb
110
+ - lib/passwordbox.rb
85
111
  - test/data/data.txt
86
112
  - test/data/data2.txt
87
- - test/data/one/data.txt.gpg
88
- - test/data/two/backup.txt.gpg
89
- - test/data/two/data.txt.gpg
113
+ - test/data/one/data.txt.crypt
114
+ - test/data/two/backup.txt.crypt
115
+ - test/data/two/data.txt.crypt
90
116
  - test/everything
91
117
  - test/hiera-file.yaml
92
118
  - test/hiera-inline.yaml
@@ -94,34 +120,35 @@ files:
94
120
  homepage: https://github.com/zenazn/hiera-crypt
95
121
  licenses:
96
122
  - MIT
97
- metadata: {}
98
123
  post_install_message:
99
124
  rdoc_options: []
100
125
  require_paths:
101
126
  - lib
102
127
  required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
103
129
  requirements:
104
- - - '>='
130
+ - - ! '>='
105
131
  - !ruby/object:Gem::Version
106
132
  version: '0'
107
133
  required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
108
135
  requirements:
109
- - - '>='
136
+ - - ! '>='
110
137
  - !ruby/object:Gem::Version
111
138
  version: '0'
112
139
  requirements: []
113
140
  rubyforge_project:
114
- rubygems_version: 2.0.0
141
+ rubygems_version: 1.8.23
115
142
  signing_key:
116
- specification_version: 4
143
+ specification_version: 3
117
144
  summary: A data backend for Hiera that returns the decrypted contents of files. Useful
118
145
  for secrets.
119
146
  test_files:
120
147
  - test/data/data.txt
121
148
  - test/data/data2.txt
122
- - test/data/one/data.txt.gpg
123
- - test/data/two/backup.txt.gpg
124
- - test/data/two/data.txt.gpg
149
+ - test/data/one/data.txt.crypt
150
+ - test/data/two/backup.txt.crypt
151
+ - test/data/two/data.txt.crypt
125
152
  - test/everything
126
153
  - test/hiera-file.yaml
127
154
  - test/hiera-inline.yaml
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: f4f9ddadfa2194dd22b7bb21f285ad2aaa1c8f53
4
- data.tar.gz: 5c8f3883285be5be667491484cd41a02d8137d08
5
- SHA512:
6
- metadata.gz: da6f2ad40a25a9b6098eeed5a4cfbe8393b7ad7ed9d3d882ccf72454d993b9eb6e60a917aae1ee146f19aa0ccc00262e095be71afb6084f29aee9905601ba3b0
7
- data.tar.gz: 95becd1d2756194762e70c1e7f8924a1d808402e419138d9d874eb37a011f11f11b6f46e81bf7ef651e0bf9bdd08710814235cd855707ecf48a7d6c82ff859d0
Binary file
Binary file
Binary file