hiera-eyaml-vault_rs 1.2.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2e55bd23a41143fe641654c75c61936401f98a7de0fe4835a7261f6199eea043
4
+ data.tar.gz: a5669657795470fff19b4769b1b20c28ab0d2d1be47ab19ad8c049fed36821b0
5
+ SHA512:
6
+ metadata.gz: 983ab8eb12b8296a0ea57249ec500f6d8222b07b6992afb971966621e8e2d67110508b3f369e0a1a395bfe0489dab91cc29aa7c5b3e03cf604555e19bdb132ca
7
+ data.tar.gz: 6ce2265df3b4eb256e4105ed1928cf63ea328e560777d2ce06a1ed76a29b20220ed5b4132ff6ef95e804eba09580e338836e67c052c2884fe10f627a11a06156
@@ -0,0 +1,3 @@
1
+ require 'hiera/backend/eyaml/encryptors/vault_rs'
2
+
3
+ Hiera::Backend::Eyaml::Encryptors::Vault_rs.register
@@ -0,0 +1,64 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'openssl'
4
+
5
+ class Hiera
6
+ module Backend
7
+ module Eyaml
8
+ module Encryptors
9
+ class Vault_rs < Encryptor
10
+ class HTTPError < Exception
11
+ end
12
+ class Httphandler
13
+ class << self
14
+
15
+ def post(uri_str, data={}, headers={}, options={})
16
+ uri = URI.parse(uri_str)
17
+ request = Net::HTTP::Post.new(uri.path)
18
+ request.body = data.to_json
19
+ http_send(uri, request, headers, options)
20
+ end
21
+
22
+ def put(uri_str, data={}, headers={}, options={})
23
+ uri = URI.parse(uri_str)
24
+ request = Net::HTTP::Put.new(uri.path)
25
+ request.body = data.to_json
26
+ http_send(uri, request, headers, options)
27
+ end
28
+
29
+
30
+ def http_send(uri, request, headers={}, options={})
31
+ request.add_field('Content-Type', options[:content_type]) if options[:content_type]
32
+
33
+ headers.each do |header, value|
34
+ request.add_field(header, value)
35
+ end
36
+
37
+ http = Net::HTTP.new(uri.host, uri.port)
38
+ if options[:cert]
39
+ http.use_ssl = true
40
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
41
+ http.cert = OpenSSL::X509::Certificate.new(options[:cert])
42
+ http.key = OpenSSL::PKey::RSA.new(options[:key])
43
+ elsif options[:ssl]
44
+ http.use_ssl = true
45
+ http.verify_mode = options[:ssl_verify] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
46
+ http.cert = OpenSSL::X509::Certificate.new(options[:ssl_cert]) if options[:ssl_cert]
47
+ http.key = OpenSSL::PKey::RSA.new(options[:ssl_key]) if options[:ssl_key]
48
+ end
49
+
50
+ begin
51
+ response = http.request(request)
52
+ return response
53
+ rescue => e
54
+ raise HTTPError, e.message
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,300 @@
1
+ require 'base64'
2
+ require 'hiera/backend/eyaml/encryptor'
3
+ require 'hiera/backend/eyaml/encryptors/vault_rs/httphandler'
4
+ require 'hiera/backend/eyaml/utils'
5
+ require 'hiera/backend/eyaml/plugins'
6
+ require 'hiera/backend/eyaml/options'
7
+
8
+ class Hiera
9
+ module Backend
10
+ module Eyaml
11
+ module Encryptors
12
+ class Vault_rs < Encryptor
13
+ class AuthenticationError < Exception
14
+ end
15
+
16
+ HTTP_HANDLER = Hiera::Backend::Eyaml::Encryptors::Vault_rs::Httphandler
17
+
18
+ self.tag = 'VAULT'
19
+
20
+ self.options = {
21
+ :addr => {
22
+ desc: "Address of the vault server",
23
+ type: :string,
24
+ default: "https://127.0.0.1:8200"
25
+ },
26
+
27
+ :role_id => {
28
+ desc: "role_id for the Approle",
29
+ type: :string,
30
+ },
31
+
32
+ :secret_id => {
33
+ desc: "secret_id for the Approle",
34
+ type: :string,
35
+ },
36
+
37
+ :auth_name => {
38
+ desc: "Name for certificate-based authentication",
39
+ type: :string,
40
+ },
41
+
42
+ :client_cert => {
43
+ desc: "Path to the client certificate for certificate-based authentication",
44
+ type: :string,
45
+ },
46
+
47
+ :client_key => {
48
+ desc: "Path to the client private key for certificate-based authentication",
49
+ type: :string,
50
+ },
51
+
52
+ :use_ssl => {
53
+ desc: "Use SSL to connect to vault",
54
+ type: :boolean,
55
+ default: true
56
+ },
57
+
58
+ :ssl_verify => {
59
+ desc: "Verify SSL certs",
60
+ type: :boolean,
61
+ default: true
62
+ },
63
+
64
+ :ssl_cert => {
65
+ desc: "SSL Certificate to connect with",
66
+ type: :string
67
+ },
68
+
69
+ :ssl_key => {
70
+ desc: "SSL Private key to connect with",
71
+ type: :string
72
+ },
73
+
74
+ :transit_name => {
75
+ desc: "Vault transit engine name (default 'transit')",
76
+ type: :string,
77
+ default: "transit"
78
+ },
79
+
80
+ :key_name => {
81
+ desc: "Vault transit key name (default 'hiera')",
82
+ type: :string,
83
+ default: "hiera"
84
+ },
85
+
86
+ :api_version => {
87
+ desc: "API version to use",
88
+ type: :integer,
89
+ default: 1
90
+ }
91
+ }
92
+ class << self
93
+
94
+ def config_file
95
+ ENV['EYAML_CONFIG'] || File.join(ENV['HOME'], '.eyaml/config.yaml') || '/etc/eyaml/config.yaml'
96
+ end
97
+
98
+ def load_config
99
+ if File.exists?(config_file)
100
+ @config_defaults = YAML.load_file(config_file)
101
+ end
102
+ end
103
+
104
+ # Allow the inherited options method to allow for local
105
+ # configuration to fall back on
106
+ #
107
+ def option(key)
108
+ # Debug flag
109
+ debug = ENV['EYAML_DEBUG'] == 'true'
110
+ puts "Resolving option for key: #{key}" if debug
111
+
112
+ # Load the configuration file if not already loaded
113
+ load_config if @config_defaults.nil?
114
+
115
+ # Try to resolve the option from the configuration file first
116
+ unless @config_defaults.nil?
117
+ config_option = @config_defaults[key.to_s]
118
+ if config_option
119
+ puts "Resolved from config_defaults: #{config_option}" if debug
120
+ return config_option
121
+ end
122
+ end
123
+
124
+ # If no value is found, fallback to super
125
+ puts "Falling back to super for key: #{key}" if debug
126
+ super
127
+ end
128
+
129
+ def create_keys
130
+ diagnostic_message = self.option :diagnostic_message
131
+ puts "Create_keys: #{diagnostic_message}"
132
+ end
133
+
134
+ def vault_url(endpoint)
135
+ uri = []
136
+ uri << option(:addr)
137
+ uri << "v#{option :api_version}"
138
+ uri << endpoint
139
+ uri.flatten.join("/")
140
+ end
141
+
142
+ def login
143
+ if option(:role_id)
144
+ role_id = option :role_id
145
+ secret_id = option :secret_id
146
+
147
+ login_data = { "role_id" => role_id }
148
+ login_data['secret_id'] = secret_id unless secret_id.nil?
149
+
150
+ response = vault_post(login_data, :login, false)
151
+ @login_token = response['auth']['client_token']
152
+ elsif option(:client_cert)
153
+ auth_name = option :auth_name
154
+
155
+ login_data = { "name" => auth_name }
156
+
157
+ response = vault_post(login_data, :cert_login, false)
158
+ @login_token = response['auth']['client_token']
159
+ end
160
+ end
161
+
162
+ def ssl?
163
+ option :use_ssl
164
+ end
165
+
166
+ def read_file(file)
167
+ raise Exception, "Cannot read #{file}" unless File.exists?(file)
168
+ File.read(file)
169
+ end
170
+
171
+ def ssl_key
172
+ return nil if option(:ssl_key).nil?
173
+ @vault_ssl_key ||= read_file(option :ssl_key)
174
+ @vault_ssl_key
175
+ end
176
+
177
+ def ssl_cert
178
+ return nil if option(:ssl_cert).nil?
179
+ @vault_ssl_cert ||= read_file(option :ssl_cert)
180
+ @vault_ssl_cert
181
+ end
182
+
183
+ def client_cert
184
+ return nil if option(:client_cert).nil?
185
+ @vault_client_cert ||= read_file(option :client_cert)
186
+ @vault_client_cert
187
+ end
188
+
189
+ def client_key
190
+ return nil if option(:client_key).nil?
191
+ @vault_client_key ||= read_file(option :client_key)
192
+ @vault_client_key
193
+ end
194
+
195
+ def token_configured?
196
+ return true if ENV['VAULT_TOKEN']
197
+ not option(:token).nil?
198
+ end
199
+
200
+ def token
201
+ authenticate
202
+ ENV['VAULT_TOKEN'] || option(:token) || @login_token
203
+ end
204
+
205
+ def authenticate
206
+ unless token_configured?
207
+ login if @login_token.nil?
208
+ end
209
+ end
210
+
211
+ def endpoint(action)
212
+ # Check for the existence of a DEBUG environment variable
213
+ debug = ENV['EYAML_DEBUG'] == 'true'
214
+
215
+ # Compute the endpoints
216
+ endpoints = {
217
+ :decrypt => "#{option(:transit_name)}/decrypt/#{option(:key_name)}",
218
+ :encrypt => "#{option(:transit_name)}/encrypt/#{option(:key_name)}",
219
+ :login => "auth/approle/login",
220
+ :cert_login => "auth/cert/login"
221
+ }
222
+
223
+ # Output debug information if the debug mode is enabled
224
+ if debug
225
+ puts "Decrypt Endpoint: #{endpoints[:decrypt]}"
226
+ puts "Encrypt Endpoint: #{endpoints[:encrypt]}"
227
+ end
228
+
229
+ endpoints[action]
230
+ end
231
+
232
+ def url_path(action)
233
+ vault_url(endpoint(action))
234
+ end
235
+
236
+ def parse_response(response)
237
+ body = JSON.load(response.body)
238
+ if response.code_type == Net::HTTPOK
239
+ return body
240
+ else
241
+ if response.code == "403"
242
+ raise AuthenticationError, body
243
+ end
244
+ if body['errors'].is_a?(Array)
245
+ message = body['errors'].join("\n")
246
+ else
247
+ message = "Failed to decrypt entry #{body}"
248
+ end
249
+ raise Exception, "Error decrypting data from Vault: #{message}"
250
+ end
251
+ end
252
+
253
+ def vault_post(data, action, use_token=true, headers={})
254
+ url = url_path(action)
255
+ http_options = {}
256
+ if option(:client_cert)
257
+ http_options = {
258
+ :cert => client_cert,
259
+ :key => client_key,
260
+ }
261
+ elsif ssl?
262
+ http_options = {
263
+ :ssl => true,
264
+ :ssl_verify => option(:ssl_verify),
265
+ :ssl_cert => ssl_cert,
266
+ :ssl_key => ssl_key,
267
+ }
268
+ end
269
+
270
+ begin
271
+ tries ||= 0
272
+ headers['X-Vault-Token'] = token if use_token
273
+ parse_response HTTP_HANDLER.post(url, data, headers, http_options)
274
+ rescue AuthenticationError => e
275
+ login
276
+ retry if (tries += 1) < 2
277
+ raise
278
+ rescue HTTPError => e
279
+ raise Exception, "HTTP Error: #{e}"
280
+ end
281
+ end
282
+
283
+ def decrypt(string)
284
+ response = vault_post({ 'ciphertext' => string}, :decrypt)
285
+ response_data=response['data']
286
+ Base64.decode64(response_data['plaintext'])
287
+ end
288
+
289
+ def encrypt(plain)
290
+ encoded = Base64.encode64(plain)
291
+ response = vault_post({ 'plaintext' => encoded}, :encrypt)
292
+ response_data=response['data']
293
+ response_data['ciphertext']
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hiera-eyaml-vault_rs
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - ryan-scheinberg
8
+ - Craig Dunn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2023-09-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: hiera-eyaml
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "<"
19
+ - !ruby/object:Gem::Version
20
+ version: 4.0.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "<"
26
+ - !ruby/object:Gem::Version
27
+ version: 4.0.0
28
+ description: Eyaml plugin for Vault transit secrets engine. Forked from https://github.com/crayfishx/hiera-eyaml-vault
29
+ email:
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/hiera/backend/eyaml/encryptors/vault_rs.rb
35
+ - lib/hiera/backend/eyaml/encryptors/vault_rs/eyaml_init.rb
36
+ - lib/hiera/backend/eyaml/encryptors/vault_rs/httphandler.rb
37
+ homepage: https://github.com/ryan-scheinberg/hiera-eyaml-vault
38
+ licenses:
39
+ - Apache-2.0
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.0.3.1
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Encryption plugin for hiera-eyaml to use Vault's transit secrets engine
60
+ test_files: []