hiera-eyaml-vault_rs 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []