hiera-vault-less-noise 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d631946488c613044259758ec2de506488ca21732a477c32756ddb5f48cc0ebd
4
+ data.tar.gz: 677fe4a0ceaf17b79464e93a52a6ac1af730afdc9cc4048d9a795248d4c47127
5
+ SHA512:
6
+ metadata.gz: 41ffd67979afbfeb45eba510ad22909275086235dfad52727c9c30ac06a9cf8560cbcf2f5103cdc241460a0f78992461c6169a1eae554f553629afaeabbaa099
7
+ data.tar.gz: 66c0f56b836cc81fd5a59ddbaadcaade1cf9f35b839e68c8d7fe9ca4c07b9bbb8a89ee91e31dc3eae9e53d821864b320cad30db5c13b899d6c44e33e0c20f4e8
@@ -0,0 +1,267 @@
1
+ # Vault backend for Hiera
2
+ class Hiera
3
+ module Backend
4
+ class Vault_backend
5
+
6
+ def initialize()
7
+ require 'json'
8
+ require 'vault'
9
+ Hiera.debug("Hiera VAULT backend starting")
10
+
11
+ @config = Config[:vault]
12
+ @config[:mounts] ||= {}
13
+ @config[:mounts][:generic] ||= ['secret']
14
+ @config[:default_field_parse] ||= 'string' # valid values: 'string', 'json'
15
+
16
+ # :override_behavior:
17
+ # Valid values: 'normal', 'flag'
18
+ # Default: 'normal'
19
+ # If set to 'flag' a read from vault will only be done if the override parameter
20
+ # is a hash, and it contains the 'flag', it will behave like this:
21
+ # - when the value of the 'flag' key is 'vault', it will look in vault
22
+ # - when the value is 'vault_only', it will return the default or raise an exception
23
+ # if the lookup key is not found in vault
24
+ # If the 'flag' key does not exist, or if the override parameter is not a hash,
25
+ # nil will be returned to signal that the next backend should be searched.
26
+ # If the hash contains the 'override' key, its value will be used as the actual
27
+ # override.
28
+ # To support the 'flag' behavior, the `hiera_vault`, `hiera_vault_array`, and
29
+ # `hiera_vault_hash` functions need to be used, since they will make sure the
30
+ # override parameter is checked and changed where needed
31
+ # Additionally, when 'vault_only' is used, it will only work properly using the
32
+ # special hiera_vault* functions
33
+ #
34
+ # The 'flag_default' setting can be used to set the default for the 'flag' element
35
+ # to 'vault_only'. This is handled by the hiera_vault* parser functions.
36
+ #
37
+ @config[:override_behavior] ||= 'normal'
38
+ if not ['normal','flag'].include?(@config[:override_behavior])
39
+ raise Exception, "[hiera-vault] invalid value for :override_behavior: '#{@config[:override_behavior]}', should be one of 'normal','flag'"
40
+ end
41
+
42
+ @config[:flag_default] ||= 'vault_first'
43
+ if not ['vault_first','vault_only'].include?(@config[:flag_default])
44
+ raise Exception, "hiera_vault: invalid value '#{@config[:flag_default]}' for :flag_default in hiera config, one of 'vault_first', 'vault_only' expected"
45
+ end
46
+
47
+ @config[:default_field_parse] ||= 'string' # valid values: 'string', 'json'
48
+ if not ['string','json'].include?(@config[:default_field_parse])
49
+ raise Exception, "[hiera-vault] invalid value for :default_field_parse: '#{@config[:default_field_behavior]}', should be one of 'string','json'"
50
+ end
51
+
52
+ # :default_field_behavior:
53
+ # 'ignore' => ignore additional fields, if the field is not present return nil
54
+ # 'only' => only return value of default_field when it is present and the only field, otherwise return hash as normal
55
+ @config[:default_field_behavior] ||= 'ignore'
56
+ if not ['ignore','only'].include?(@config[:default_field_behavior])
57
+ raise Exception, "[hiera-vault] invalid value for :default_field_behavior: '#{@config[:default_field_behavior]}', should be one of 'ignore','only'"
58
+ end
59
+
60
+ vault_connect
61
+ end
62
+
63
+ def lookup(key, scope, order_override, resolution_type)
64
+ begin
65
+ vault_connect
66
+
67
+ read_vault = false
68
+ genpw = false
69
+ otp = nil
70
+
71
+ if @config[:override_behavior] == 'flag'
72
+ if order_override.kind_of? Hash
73
+ if order_override.has_key?('flag')
74
+ if ['vault_default','vault_first','vault_only'].include?(order_override['flag'])
75
+ read_vault = true
76
+ if order_override['flag'] == 'vault_default'
77
+ # since variables are passed by reference, the caller will know afterwards, which flag was actually used
78
+ order_override['flag'] = @config[:flag_default]
79
+ end
80
+ if order_override.has_key?('generate')
81
+ pwlen = order_override['generate'].to_i
82
+ if pwlen > 8 # TODO: make configurable
83
+ genpw = true
84
+ end
85
+ end
86
+ if order_override.has_key?('vault_otp')
87
+ otp = order_override['vault_otp']
88
+ end
89
+ if order_override.has_key?('resolution_type')
90
+ resolution_type = order_override['resolution_type']
91
+ end
92
+ # this one must be last, because order_override gets a new value!:
93
+ if order_override.has_key?('override')
94
+ order_override = order_override['override']
95
+ else
96
+ order_override = nil
97
+ end
98
+ else
99
+ raise Exception, "[hiera-vault] Invalid value '#{order_override['flag']}' for 'flag' element in override parameter, expected one of ['vault_default', 'vault_first', 'vault_only'], while override_behavior is 'flag'"
100
+ end
101
+ if @vault.nil?
102
+ raise Exception, "[hiera-vault] Cannot skip, because vault is unavailable and vault must be read, while override_behavior is 'flag'"
103
+ end
104
+ else
105
+ Hiera.debug("[hiera-vault] Not reading from vault, because 'flag' element does not exist in override parameter, while override_behavior is 'flag'")
106
+ end
107
+ else
108
+ Hiera.debug("[hiera-vault] Not reading from vault, because override parameter is not a hash, while override_behavior is 'flag'")
109
+ end
110
+ else
111
+ # normal behavior
112
+ return nil if @vault.nil?
113
+ read_vault = true
114
+ end
115
+
116
+ answer = nil
117
+
118
+ if read_vault
119
+ Hiera.debug("[hiera-vault] Looking up #{key} in vault backend")
120
+
121
+ found = false
122
+
123
+ # Only generic mounts supported so far
124
+ @config[:mounts][:generic].each do |mount|
125
+ path = Backend.parse_string(mount, scope, { 'key' => key })
126
+ Backend.datasources(scope, order_override) do |source|
127
+ Hiera.debug("Looking in path #{path}/#{source}/")
128
+ new_answer = lookup_generic("#{path}/#{source}/#{key}", scope)
129
+ #Hiera.debug("[hiera-vault] Answer: #{new_answer}:#{new_answer.class}")
130
+ next if new_answer.nil?
131
+ case resolution_type
132
+ when :array
133
+ raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
134
+ answer ||= []
135
+ answer << new_answer
136
+ when :hash
137
+ raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
138
+ answer ||= {}
139
+ answer = Backend.merge_answer(new_answer,answer)
140
+ else
141
+ answer = new_answer
142
+ found = true
143
+ break
144
+ end
145
+ end
146
+
147
+ break if found
148
+ end
149
+ end
150
+
151
+ if answer.nil? and @config[:default_field] and genpw
152
+ new_answer = generate(pwlen)
153
+
154
+ @config[:mounts][:generic].each do |mount|
155
+ path = Backend.parse_string(mount, scope, { 'key' => key })
156
+ Backend.datasources(scope, order_override) do |source|
157
+ # Storing the generated secret in the override path or the highest path in the hierarchy
158
+ # make sure to use a proper override or an appropriate hierarchy if the secret is to be used
159
+ # on different nodes, otherwise the same key might be written with a different value at different
160
+ # paths
161
+ Hiera.debug("Storing generated secret in vault at path #{path}/#{source}/#{key}")
162
+ answer = new_answer if store("#{path}/#{source}/#{key}", { @config[:default_field].to_sym => new_answer })
163
+ break
164
+ end
165
+ break
166
+ end
167
+ end
168
+ if answer.nil? and not otp.nil?
169
+ answer = otp
170
+ end
171
+ return answer
172
+ rescue Exception => e
173
+ raise Exception, "#{e.message} in #{e.backtrace[0]}"
174
+ end
175
+ end
176
+
177
+ def vault_connect
178
+ if @vault.nil?
179
+ begin
180
+ @vault = Vault::Client.new
181
+ @vault.configure do |config|
182
+ config.address = @config[:addr] if @config[:addr]
183
+ config.token = @config[:token] if @config[:token]
184
+ config.ssl_pem_file = @config[:ssl_pem_file] if @config[:ssl_pem_file]
185
+ config.ssl_verify = @config[:ssl_verify] if @config[:ssl_verify]
186
+ config.ssl_ca_cert = @config[:ssl_ca_cert] if config.respond_to? :ssl_ca_cert
187
+ config.ssl_ca_path = @config[:ssl_ca_path] if config.respond_to? :ssl_ca_path
188
+ config.ssl_ciphers = @config[:ssl_ciphers] if config.respond_to? :ssl_ciphers
189
+ end
190
+
191
+ fail if @vault.sys.seal_status.sealed?
192
+ Hiera.debug("[hiera-vault] Client configured to connect to #{@vault.address}")
193
+ rescue Exception => e
194
+ @vault = nil
195
+ Hiera.warn("[hiera-vault] Skipping backend. Configuration error: #{e}")
196
+ end
197
+ end
198
+ if @vault
199
+ begin
200
+ fail if @vault.sys.seal_status.sealed?
201
+ rescue Exception => e
202
+ @vault = nil
203
+ Hiera.warn("[hiera-vault] Vault is unavailable or configuration error: #{e}")
204
+ end
205
+ end
206
+ end
207
+
208
+ def lookup_generic(key, scope)
209
+ begin
210
+ secret = @vault.logical.read(key)
211
+ rescue Exception => e
212
+ raise Exception, "[hiera-vault] Could not read secret #{key}, #{e.class}: #{e.errors.join("\n").rstrip}"
213
+ end
214
+
215
+ return nil if secret.nil?
216
+
217
+ Hiera.debug("[hiera-vault] Read secret: #{key}")
218
+ if @config[:default_field] and (@config[:default_field_behavior] == 'ignore' or (secret.data.has_key?(@config[:default_field].to_sym) and secret.data.length == 1))
219
+ return nil if not secret.data.has_key?(@config[:default_field].to_sym)
220
+ # Return just our default_field
221
+ data = secret.data[@config[:default_field].to_sym]
222
+ if @config[:default_field_parse] == 'json'
223
+ begin
224
+ data = JSON.parse(data)
225
+ rescue JSON::ParserError
226
+ Hiera.debug("[hiera-vault] Could not parse string as JSON")
227
+ end
228
+ end
229
+ else
230
+ # Turn secret's hash keys into strings
231
+ data = secret.data.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
232
+ end
233
+ #Hiera.debug("[hiera-vault] Data: #{data}:#{data.class}")
234
+
235
+ return Backend.parse_answer(data, scope)
236
+ end
237
+
238
+ def generate(password_size)
239
+ pass = ""
240
+ (1..password_size).each do
241
+ pass += (("a".."z").to_a+("A".."Z").to_a+("0".."9").to_a)[rand(62).to_int]
242
+ end
243
+
244
+ pass
245
+ end
246
+
247
+ def store(key, secret_hash)
248
+ begin
249
+ write_result = @vault.logical.write(key, secret_hash)
250
+ rescue Vault::HTTPConnectionError
251
+ Hiera.debug("[hiera-vault] Could not connect to write secret: #{key}")
252
+ rescue Vault::HTTPError => e
253
+ Hiera.warn("[hiera-vault] Could not write secret #{key}: #{e.errors.join("\n").rstrip}")
254
+ end
255
+
256
+ if write_result == true
257
+ Hiera.debug("[hiera-vault] Successfully written secret: #{key}")
258
+ return true
259
+ else
260
+ Hiera.warn("[hiera-vault] Could not write secret #{key}: #{write_result}")
261
+ return false
262
+ end
263
+ end
264
+
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,122 @@
1
+ require 'hiera_puppet'
2
+
3
+ module HieraVault
4
+
5
+ module_function
6
+ def lookup(key, default, scope, override, resolution_type)
7
+ begin
8
+ flag_default = 'vault_default'
9
+ override ||= {'flag' => flag_default}
10
+ case override.class.to_s
11
+ when 'String'
12
+ override = {'flag' => flag_default, 'override' => override}
13
+ when 'Hash'
14
+ if not override.has_key?('flag')
15
+ override['flag'] = flag_default
16
+ end
17
+ else
18
+ raise(Puppet::ParseError, "hiera_vault: invalid 'override' parameter supplied: '#{override}':#{override.class}")
19
+ end
20
+
21
+ if resolution_type == :priority
22
+ if default.kind_of? Hash
23
+ if default.has_key?('generate')
24
+ override['generate'] = default['generate'].to_i
25
+ default = nil
26
+ end
27
+ end
28
+ end
29
+
30
+ r = rand(2147483647).to_s
31
+ otp = "vault_otp_#{r}"
32
+ case resolution_type
33
+ when :array
34
+ otp = [otp]
35
+ when :hash
36
+ otp = {'otp' => otp}
37
+ end
38
+ override['vault_otp'] = otp
39
+
40
+ # this is for vault_backend so that it will use the actual resolution type internally
41
+ override['resolution_type'] = resolution_type
42
+
43
+ new_answer = HieraPuppet.lookup(key, nil, scope, override, :priority)
44
+ if new_answer == otp
45
+ # this means that vault_backend could not find anything, so it returned the value of vault_otp
46
+ new_answer = nil
47
+ end
48
+
49
+ if new_answer.nil?
50
+ answer = nil
51
+ else
52
+ case resolution_type
53
+ when :array
54
+ raise Puppet::ParseError, "hiera_vault: after vault_backend.lookup: type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
55
+ answer ||= []
56
+ answer << new_answer
57
+ when :hash
58
+ raise Puppet::ParseError, "hiera_vault: after vault_backend.lookup: type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
59
+ answer ||= {}
60
+ answer = Hiera::Backend.merge_answer(new_answer,answer)
61
+ else
62
+ answer = new_answer
63
+ end
64
+ end
65
+
66
+ hiera_scope = Hiera::Scope.new(scope)
67
+ if override['flag'] == 'vault_only'
68
+ if not (default.nil? or default.empty?)
69
+ answer = Hiera::Backend.resolve_answer(answer, resolution_type) unless answer.nil?
70
+ answer = Hiera::Backend.parse_string(default, hiera_scope) if answer.nil? and default.is_a?(String)
71
+ answer = default if answer.nil?
72
+ end
73
+ if answer.nil?
74
+ raise(Puppet::ParseError, "hiera_vault: Could not find data item #{key} in vault, while vault_only was requested, and empty default supplied")
75
+ end
76
+ return answer
77
+ end
78
+
79
+ if answer.nil? or resolution_type != :priority
80
+ # continue with normal hiera lookup, while vault_backend will skip automatically
81
+ if override.has_key?('override')
82
+ override = override['override']
83
+ else
84
+ override = nil
85
+ end
86
+
87
+ begin
88
+ new_answer = HieraPuppet.lookup(key, nil, scope, override, resolution_type)
89
+ rescue Puppet::ParseError
90
+ if answer.nil?
91
+ if default.nil? or default.empty?
92
+ raise(Puppet::ParseError, "Could not find data item #{key} in vault and in any Hiera data file and no or empty default supplied")
93
+ end
94
+ answer = Hiera::Backend.parse_string(default, hiera_scope) if default.is_a?(String)
95
+ answer = default if answer.nil?
96
+ return answer
97
+ end
98
+ end
99
+ end
100
+ if not new_answer.nil?
101
+ case resolution_type
102
+ when :array
103
+ raise Puppet::ParseError, "hiera_vault: after normal Hiera lookup: type mismatch: expected Array and got #{new_answer.class}" unless new_answer.nil? or new_answer.kind_of? Array or new_answer.kind_of? String
104
+ answer ||= []
105
+ answer << new_answer
106
+ when :hash
107
+ raise Puppet::ParseError, "hiera_vault: after normal Hiera lookup: type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
108
+ answer ||= {}
109
+ answer = Hiera::Backend.merge_answer(new_answer,answer)
110
+ else
111
+ answer = new_answer
112
+ end
113
+ end
114
+ answer = Hiera::Backend.resolve_answer(answer, resolution_type)
115
+ return answer
116
+ rescue Exception => e
117
+ raise(Puppet::ParseError, "#{e.message} in #{e.backtrace[0]}")
118
+ end
119
+ end
120
+
121
+ end
122
+
@@ -0,0 +1,17 @@
1
+ require 'hiera_puppet'
2
+ require 'hiera_vault'
3
+
4
+ module Puppet::Parser::Functions
5
+ newfunction(:hiera_vault, :type => :rvalue, :arity => -2, :doc => "Performs a
6
+ hiera lookup, first and optionally only in the 'vault' backend.
7
+
8
+ The behavior depends on the 'override' parameter.
9
+
10
+ NOTICE: For this function to work properly, set :override_behavior: 'flag' in the
11
+ :vault: config part in the hiera config.
12
+ ") do |*args|
13
+ key, default, override = HieraPuppet.parse_args(args)
14
+ HieraVault.lookup(key, default, self, override, :priority)
15
+ end
16
+ end
17
+
@@ -0,0 +1,17 @@
1
+ require 'hiera_puppet'
2
+ require 'hiera_vault'
3
+
4
+ module Puppet::Parser::Functions
5
+ newfunction(:hiera_vault_array, :type => :rvalue, :arity => -2, :doc => "Performs a
6
+ hiera_array lookup, first and optionally only in the 'vault' backend.
7
+
8
+ The behavior depends on the 'override' parameter.
9
+
10
+ NOTICE: For this function to work properly, set :override_behavior: 'flag' in the
11
+ :vault: config part in the hiera config.
12
+ ") do |*args|
13
+ key, default, override = HieraPuppet.parse_args(args)
14
+ HieraVault.lookup(key, default, self, override, :array)
15
+ end
16
+ end
17
+
@@ -0,0 +1,17 @@
1
+ require 'hiera_puppet'
2
+ require 'hiera_vault'
3
+
4
+ module Puppet::Parser::Functions
5
+ newfunction(:hiera_vault_hash, :type => :rvalue, :arity => -2, :doc => "Performs a
6
+ hiera_hash lookup, first and optionally only in the 'vault' backend.
7
+
8
+ The behavior depends on the 'override' parameter.
9
+
10
+ NOTICE: For this function to work properly, set :override_behavior: 'flag' in the
11
+ :vault: config part in the hiera config.
12
+ ") do |*args|
13
+ key, default, override = HieraPuppet.parse_args(args)
14
+ HieraVault.lookup(key, default, self, override, :hash)
15
+ end
16
+ end
17
+
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hiera-vault-less-noise
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Sokolowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-04-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: vault
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ description: Hiera backend for looking up secrets stored in Vault
28
+ email: jonathan.sokolowski@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/hiera/backend/vault_backend.rb
34
+ - lib/hiera_vault.rb
35
+ - lib/puppet/parser/functions/hiera_vault.rb
36
+ - lib/puppet/parser/functions/hiera_vault_array.rb
37
+ - lib/puppet/parser/functions/hiera_vault_hash.rb
38
+ homepage: http://github.com/tylerjl/hiera-vault
39
+ licenses:
40
+ - Apache-2.0
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.7.3
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Module for using vault as a hiera backend
62
+ test_files: []