hybrid_platforms_conductor 33.2.2 → 33.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -0
  3. data/README.md +29 -2
  4. data/docs/config_dsl.md +45 -0
  5. data/docs/plugins.md +1 -0
  6. data/docs/plugins/secrets_reader/keepass.md +62 -0
  7. data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
  8. data/lib/hybrid_platforms_conductor/cmd_runner.rb +4 -4
  9. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
  10. data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
  11. data/lib/hybrid_platforms_conductor/config.rb +0 -35
  12. data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
  13. data/lib/hybrid_platforms_conductor/connector.rb +1 -1
  14. data/lib/hybrid_platforms_conductor/core_extensions/bundler/without_bundled_env.rb +18 -1
  15. data/lib/hybrid_platforms_conductor/credentials.rb +122 -97
  16. data/lib/hybrid_platforms_conductor/deployer.rb +37 -30
  17. data/lib/hybrid_platforms_conductor/github.rb +39 -0
  18. data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +1 -1
  19. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +27 -17
  20. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +4 -2
  21. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +1 -1
  22. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +29 -20
  23. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +1 -1
  24. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +5 -3
  25. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/reserve_proxmox_container +1 -0
  26. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
  27. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +174 -0
  28. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
  29. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
  30. data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
  31. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +7 -3
  32. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +8 -4
  33. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
  34. data/lib/hybrid_platforms_conductor/logger_helpers.rb +24 -1
  35. data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
  36. data/lib/hybrid_platforms_conductor/safe_merge.rb +37 -0
  37. data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
  38. data/lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb +5 -3
  39. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  40. data/spec/hybrid_platforms_conductor_test.rb +10 -0
  41. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +15 -0
  42. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +32 -0
  43. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +9 -0
  44. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +8 -6
  45. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +38 -0
  46. data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +21 -1
  47. data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +48 -72
  48. data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +251 -0
  49. data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +36 -0
  50. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +680 -0
  51. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
  52. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs_plugins_api_spec.rb +2 -2
  53. data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +1 -1
  54. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
  55. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
  56. data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +21 -15
  57. data/spec/hybrid_platforms_conductor_test/test_connector.rb +2 -2
  58. metadata +188 -139
@@ -8,6 +8,42 @@ describe HybridPlatformsConductor::Deployer do
8
8
  end
9
9
  end
10
10
 
11
+ it 'returns the retriable errors correctly' do
12
+ with_platforms(
13
+ <<~EO_CONFIG
14
+ retry_deploy_for_errors_on_stdout 'Retry stdout global'
15
+ retry_deploy_for_errors_on_stderr [
16
+ 'Retry stderr global',
17
+ /.+Retry stderr regexp global/
18
+ ]
19
+ for_nodes(%w[node1 node2 node3]) do
20
+ retry_deploy_for_errors_on_stdout 'Retry stdout nodes'
21
+ retry_deploy_for_errors_on_stderr 'Retry stderr nodes'
22
+ end
23
+ EO_CONFIG
24
+ ) do
25
+ sort_proc = proc { |retriable_error_info| ((retriable_error_info[:errors_on_stdout] || []) + (retriable_error_info[:errors_on_stderr] || [])).first.to_s }
26
+ expect(test_config.retriable_errors.sort_by(&sort_proc)).to eq [
27
+ {
28
+ nodes_selectors_stack: [],
29
+ errors_on_stdout: ['Retry stdout global']
30
+ },
31
+ {
32
+ nodes_selectors_stack: [],
33
+ errors_on_stderr: ['Retry stderr global', /.+Retry stderr regexp global/]
34
+ },
35
+ {
36
+ nodes_selectors_stack: [%w[node1 node2 node3]],
37
+ errors_on_stdout: ['Retry stdout nodes']
38
+ },
39
+ {
40
+ nodes_selectors_stack: [%w[node1 node2 node3]],
41
+ errors_on_stderr: ['Retry stderr nodes']
42
+ }
43
+ ].sort_by(&sort_proc)
44
+ end
45
+ end
46
+
11
47
  it 'declares log plugins to be used' do
12
48
  with_platforms(
13
49
  <<~EO_CONFIG
@@ -0,0 +1,680 @@
1
+ require 'open3'
2
+
3
+ describe HybridPlatformsConductor::Deployer do
4
+
5
+ context 'when checking secrets_reader plugins' do
6
+
7
+ context 'with keepass' do
8
+
9
+ # Expect some calls to be done on KPScript
10
+ #
11
+ # Parameters::
12
+ # * *expected_calls* (Array<[String, String or Hash]>): The list of calls and their corresponding mocked response:
13
+ # * String: Mocked stdout
14
+ # * Hash<Symbol,Object>: More complete structure defining the mocked response:
15
+ # * *exit_status* (Integer): The command exit status [default: 0]
16
+ # * *stdout* (String): The command stdout
17
+ # * *xml* (String or nil): XML document to generate as an export, or nil for none [default: nil]
18
+ def expect_calls_to_kpscript(expected_calls)
19
+ if expected_calls.empty?
20
+ expect(Open3).not_to receive(:popen3)
21
+ else
22
+ expect(Open3).to receive(:popen3).exactly(expected_calls.size).times do |cmd, &block|
23
+ expected_call, mocked_call = expected_calls.shift
24
+ if expected_call.is_a?(Regexp)
25
+ expect(cmd).to match expected_call
26
+ else
27
+ expect(cmd).to eq expected_call
28
+ end
29
+ mocked_call = { stdout: mocked_call } if mocked_call.is_a?(String)
30
+ mocked_call[:exit_status] = 0 unless mocked_call.key?(:exit_status)
31
+ wait_thr_double = instance_double(Process::Waiter)
32
+ allow(wait_thr_double).to receive(:value) do
33
+ wait_thr_value_double = instance_double(Process::Status)
34
+ allow(wait_thr_value_double).to receive(:exitstatus) do
35
+ mocked_call[:exit_status]
36
+ end
37
+ wait_thr_value_double
38
+ end
39
+ if mocked_call[:xml]
40
+ xml_file = cmd.match(/-OutFile:"([^"]+)"/)[1]
41
+ logger.debug "Mock KPScript XML file #{xml_file} with\n#{mocked_call[:xml]}"
42
+ File.write(xml_file, mocked_call[:xml])
43
+ end
44
+ block.call(
45
+ StringIO.new,
46
+ StringIO.new(mocked_call[:stdout]),
47
+ StringIO.new,
48
+ wait_thr_double
49
+ )
50
+ end
51
+ end
52
+ end
53
+
54
+ # Setup a platform for tests
55
+ #
56
+ # Parameters::
57
+ # * *additional_config* (String): Additional config
58
+ # * *platform_info* (Hash): Platform configuration [default: 1 node having 1 service]
59
+ # * *mock_keepass_password* (String): Password to be returned by credentials [default: 'test_keepass_password']
60
+ # * *expect_key_file* (String or nil): Key file to be expected, or nil if none [default: nil]
61
+ # * *expect_password_enc* (String or nil): Encrypted password to be expected, or nil if none [default: nil]
62
+ # * *expect_kpscript_calls* (Boolean): Should we expect calls to KPScript? [default: true]
63
+ def with_test_platform_for_keepass_test(
64
+ additional_config,
65
+ platform_info: {
66
+ nodes: { 'node' => { services: %w[service] } },
67
+ deployable_services: %w[service]
68
+ },
69
+ mock_keepass_password: 'test_keepass_password',
70
+ mock_databases: { '/path/to/database.kdbx' => xml_single_entry },
71
+ expect_key_file: nil,
72
+ expect_password_enc: nil,
73
+ expect_kpscript_calls: true
74
+ )
75
+ with_test_platform(
76
+ platform_info,
77
+ additional_config: "read_secrets_from :keepass\n#{additional_config}"
78
+ ) do
79
+ mock_databases.each do |database, _xml|
80
+ expect(test_deployer.instance_variable_get(:@secrets_readers)[:keepass]).to receive(:with_credentials_for).with(:keepass, resource: database) do |_id, resource: nil, &client_code|
81
+ client_code.call nil, mock_keepass_password
82
+ end
83
+ end
84
+ if expect_kpscript_calls
85
+ expect_calls_to_kpscript(
86
+ mock_databases.map do |database, xml|
87
+ [
88
+ %r{/path/to/kpscript "#{Regexp.escape(database)}"#{mock_keepass_password.nil? ? '' : " -pw:\"#{Regexp.escape(mock_keepass_password)}\""}#{expect_password_enc.nil? ? '' : " -pw-enc:\"#{Regexp.escape(expect_password_enc)}\""}#{expect_key_file.nil? ? '' : " -keyfile:\"#{Regexp.escape(expect_key_file)}\""} -c:Export -Format:"KeePass XML \(2.x\)" -OutFile:"/tmp/.+"},
89
+ {
90
+ stdout: 'OK: Operation completed successfully.',
91
+ xml: xml
92
+ }
93
+ ]
94
+ end
95
+ )
96
+ end
97
+ yield
98
+ end
99
+ end
100
+
101
+ # Expect secrets to be set to given values
102
+ #
103
+ # Parameters::
104
+ # * *expected_secrets* (Hash): Expected secrets
105
+ def expect_secrets_to_be(expected_secrets)
106
+ expect(test_services_handler).to receive(:package).with(
107
+ services: { 'node' => %w[service] },
108
+ secrets: expected_secrets,
109
+ local_environment: false
110
+ ) { raise 'Abort as testing secrets is enough' }
111
+ expect { test_deployer.deploy_on(%w[node]) }.to raise_error 'Abort as testing secrets is enough'
112
+ end
113
+
114
+ let(:xml_single_entry) do
115
+ <<~EO_XML
116
+ <KeePassFile>
117
+ <Root>
118
+ <Group>
119
+ <Entry>
120
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
121
+ <String>
122
+ <Key>Password</Key>
123
+ <Value ProtectInMemory="True">TestPassword</Value>
124
+ </String>
125
+ <String>
126
+ <Key>Title</Key>
127
+ <Value>Test Secret</Value>
128
+ </String>
129
+ <String>
130
+ <Key>UserName</Key>
131
+ <Value>Test User Name</Value>
132
+ </String>
133
+ </Entry>
134
+ </Group>
135
+ </Root>
136
+ </KeePassFile>
137
+ EO_XML
138
+ end
139
+
140
+ it 'gets secrets from a KeePass database with password' do
141
+ with_test_platform_for_keepass_test(
142
+ <<~EO_CONFIG
143
+ use_kpscript_from '/path/to/kpscript'
144
+ secrets_from_keepass(database: '/path/to/database.kdbx')
145
+ EO_CONFIG
146
+ ) do
147
+ expect_secrets_to_be('Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' })
148
+ end
149
+ end
150
+
151
+ it 'gets secrets from a KeePass database with password and key file' do
152
+ with_test_platform_for_keepass_test(
153
+ <<~EO_CONFIG,
154
+ use_kpscript_from '/path/to/kpscript'
155
+ secrets_from_keepass(database: '/path/to/database.kdbx')
156
+ EO_CONFIG
157
+ expect_key_file: '/path/to/database.key'
158
+ ) do
159
+ ENV['hpc_key_file_for_keepass'] = '/path/to/database.key'
160
+ expect_secrets_to_be('Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' })
161
+ end
162
+ end
163
+
164
+ it 'gets secrets from a KeePass database with encrypted password' do
165
+ with_test_platform_for_keepass_test(
166
+ <<~EO_CONFIG,
167
+ use_kpscript_from '/path/to/kpscript'
168
+ secrets_from_keepass(database: '/path/to/database.kdbx')
169
+ EO_CONFIG
170
+ mock_keepass_password: nil,
171
+ expect_password_enc: 'PASSWORD_ENC'
172
+ ) do
173
+ ENV['hpc_password_enc_for_keepass'] = 'PASSWORD_ENC'
174
+ expect_secrets_to_be('Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' })
175
+ end
176
+ end
177
+
178
+ it 'gets secrets from a KeePass database with encrypted password and key file' do
179
+ with_test_platform_for_keepass_test(
180
+ <<~EO_CONFIG,
181
+ use_kpscript_from '/path/to/kpscript'
182
+ secrets_from_keepass(database: '/path/to/database.kdbx')
183
+ EO_CONFIG
184
+ mock_keepass_password: nil,
185
+ expect_password_enc: 'PASSWORD_ENC',
186
+ expect_key_file: '/path/to/database.key'
187
+ ) do
188
+ ENV['hpc_password_enc_for_keepass'] = 'PASSWORD_ENC'
189
+ ENV['hpc_key_file_for_keepass'] = '/path/to/database.key'
190
+ expect_secrets_to_be('Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' })
191
+ end
192
+ end
193
+
194
+ it 'gets secrets from a KeePass database with key file' do
195
+ with_test_platform_for_keepass_test(
196
+ <<~EO_CONFIG,
197
+ use_kpscript_from '/path/to/kpscript'
198
+ secrets_from_keepass(database: '/path/to/database.kdbx')
199
+ EO_CONFIG
200
+ mock_keepass_password: nil,
201
+ expect_key_file: '/path/to/database.key'
202
+ ) do
203
+ ENV['hpc_key_file_for_keepass'] = '/path/to/database.key'
204
+ expect_secrets_to_be('Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' })
205
+ end
206
+ end
207
+
208
+ it 'fails to get secrets from a KeePass database when no authentication mechanisms are provided' do
209
+ with_test_platform_for_keepass_test(
210
+ <<~EO_CONFIG,
211
+ use_kpscript_from '/path/to/kpscript'
212
+ secrets_from_keepass(database: '/path/to/database.kdbx')
213
+ EO_CONFIG
214
+ mock_keepass_password: nil,
215
+ expect_kpscript_calls: false
216
+ ) do
217
+ expect { test_deployer.deploy_on(%w[node]) }.to raise_error 'Please specify at least one of password, password_enc or key_file arguments'
218
+ end
219
+ end
220
+
221
+ it 'fails to get secrets if KPScript is not configured' do
222
+ with_test_platform_for_keepass_test(
223
+ <<~EO_CONFIG,
224
+ secrets_from_keepass(database: '/path/to/database.kdbx')
225
+ EO_CONFIG
226
+ mock_databases: {},
227
+ expect_kpscript_calls: false
228
+ ) do
229
+ expect { test_deployer.deploy_on(%w[node]) }.to raise_error 'Missing KPScript configuration. Please use use_kpscript_from to set it.'
230
+ end
231
+ end
232
+
233
+ it 'gets secrets from KeePass groups' do
234
+ with_test_platform_for_keepass_test(
235
+ <<~EO_CONFIG,
236
+ use_kpscript_from '/path/to/kpscript'
237
+ secrets_from_keepass(database: '/path/to/database.kdbx')
238
+ EO_CONFIG
239
+ mock_databases: {
240
+ '/path/to/database.kdbx' => <<~EO_XML
241
+ <KeePassFile>
242
+ <Root>
243
+ <Group>
244
+ <Entry>
245
+ <String>
246
+ <Key>Password</Key>
247
+ <Value ProtectInMemory="True">TestPassword0</Value>
248
+ </String>
249
+ <String>
250
+ <Key>Title</Key>
251
+ <Value>Secret 0</Value>
252
+ </String>
253
+ </Entry>
254
+ <Group>
255
+ <Name>Group1</UUID>
256
+ <Entry>
257
+ <String>
258
+ <Key>Password</Key>
259
+ <Value ProtectInMemory="True">TestPassword1</Value>
260
+ </String>
261
+ <String>
262
+ <Key>Title</Key>
263
+ <Value>Secret 1</Value>
264
+ </String>
265
+ </Entry>
266
+ <Group>
267
+ <Name>Group2</UUID>
268
+ <Entry>
269
+ <String>
270
+ <Key>Password</Key>
271
+ <Value ProtectInMemory="True">TestPassword2</Value>
272
+ </String>
273
+ <String>
274
+ <Key>Title</Key>
275
+ <Value>Secret 2</Value>
276
+ </String>
277
+ </Entry>
278
+ </Group>
279
+ <Group>
280
+ <Name>Group3</UUID>
281
+ <Entry>
282
+ <String>
283
+ <Key>Password</Key>
284
+ <Value ProtectInMemory="True">TestPassword3</Value>
285
+ </String>
286
+ <String>
287
+ <Key>Title</Key>
288
+ <Value>Secret 3</Value>
289
+ </String>
290
+ </Entry>
291
+ </Group>
292
+ </Group>
293
+ </Group>
294
+ </Root>
295
+ </KeePassFile>
296
+ EO_XML
297
+ }
298
+ ) do
299
+ expect_secrets_to_be(
300
+ 'Secret 0' => { 'password' => 'TestPassword0' },
301
+ 'Group1' => {
302
+ 'Secret 1' => { 'password' => 'TestPassword1' },
303
+ 'Group2' => {
304
+ 'Secret 2' => { 'password' => 'TestPassword2' }
305
+ },
306
+ 'Group3' => {
307
+ 'Secret 3' => { 'password' => 'TestPassword3' }
308
+ }
309
+ }
310
+ )
311
+ end
312
+ end
313
+
314
+ it 'gets secrets with attachments' do
315
+ with_test_platform_for_keepass_test(
316
+ <<~EO_CONFIG,
317
+ use_kpscript_from '/path/to/kpscript'
318
+ secrets_from_keepass(database: '/path/to/database.kdbx')
319
+ EO_CONFIG
320
+ mock_databases: {
321
+ '/path/to/database.kdbx' => <<~EO_XML
322
+ <KeePassFile>
323
+ <Meta>
324
+ <Binaries>
325
+ <Binary ID="0" Compressed="True">#{
326
+ str = StringIO.new
327
+ gz = Zlib::GzipWriter.new(str)
328
+ gz.write('File 0 Content')
329
+ gz.close
330
+ Base64.encode64(str.string).strip
331
+ }</Binary>
332
+ <Binary ID="1">#{Base64.encode64('File 1 Content').strip}</Binary>
333
+ </Binaries>
334
+ </Meta>
335
+ <Root>
336
+ <Group>
337
+ <Entry>
338
+ <String>
339
+ <Key>Password</Key>
340
+ <Value ProtectInMemory="True">TestPassword0</Value>
341
+ </String>
342
+ <String>
343
+ <Key>Title</Key>
344
+ <Value>Secret 0</Value>
345
+ </String>
346
+ <Binary>
347
+ <Key>file0.txt</Key>
348
+ <Value Ref="0" />
349
+ </Binary>
350
+ </Entry>
351
+ <Group>
352
+ <Name>Group1</UUID>
353
+ <Entry>
354
+ <String>
355
+ <Key>Password</Key>
356
+ <Value ProtectInMemory="True">TestPassword1</Value>
357
+ </String>
358
+ <String>
359
+ <Key>Title</Key>
360
+ <Value>Secret 1</Value>
361
+ </String>
362
+ <Binary>
363
+ <Key>file1.txt</Key>
364
+ <Value Ref="1" />
365
+ </Binary>
366
+ </Entry>
367
+ </Group>
368
+ </Group>
369
+ </Root>
370
+ </KeePassFile>
371
+ EO_XML
372
+ }
373
+ ) do
374
+ expect_secrets_to_be(
375
+ 'Secret 0' => { 'file0.txt' => 'File 0 Content', 'password' => 'TestPassword0' },
376
+ 'Group1' => {
377
+ 'Secret 1' => { 'file1.txt' => 'File 1 Content', 'password' => 'TestPassword1' }
378
+ }
379
+ )
380
+ end
381
+ end
382
+
383
+ it 'gets secrets from a KeePass database for several nodes' do
384
+ with_test_platform_for_keepass_test(
385
+ <<~EO_CONFIG,
386
+ use_kpscript_from '/path/to/kpscript'
387
+ secrets_from_keepass(database: '/path/to/database.kdbx')
388
+ EO_CONFIG
389
+ platform_info: {
390
+ nodes: { 'node1' => { services: %w[service1] }, 'node2' => { services: %w[service2] } },
391
+ deployable_services: %w[service1 service2]
392
+ }
393
+ ) do
394
+ expect(test_services_handler).to receive(:package).with(
395
+ services: { 'node1' => %w[service1], 'node2' => %w[service2] },
396
+ secrets: { 'Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' } },
397
+ local_environment: false
398
+ ) { raise 'Abort as testing secrets is enough' }
399
+ expect { test_deployer.deploy_on(%w[node1 node2]) }.to raise_error 'Abort as testing secrets is enough'
400
+ end
401
+ end
402
+
403
+ it 'gets secrets from a KeePass database for several databases' do
404
+ with_test_platform_for_keepass_test(
405
+ <<~EO_CONFIG,
406
+ use_kpscript_from '/path/to/kpscript'
407
+ secrets_from_keepass(database: '/path/to/database1.kdbx')
408
+ for_nodes('node2') do
409
+ secrets_from_keepass(database: '/path/to/database2.kdbx')
410
+ end
411
+ EO_CONFIG
412
+ platform_info: {
413
+ nodes: { 'node1' => { services: %w[service1] }, 'node2' => { services: %w[service2] } },
414
+ deployable_services: %w[service1 service2]
415
+ },
416
+ mock_databases: {
417
+ '/path/to/database1.kdbx' => xml_single_entry,
418
+ '/path/to/database2.kdbx' => <<~EO_XML
419
+ <KeePassFile>
420
+ <Root>
421
+ <Group>
422
+ <Entry>
423
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
424
+ <String>
425
+ <Key>Password</Key>
426
+ <Value ProtectInMemory="True">TestPassword2</Value>
427
+ </String>
428
+ <String>
429
+ <Key>Title</Key>
430
+ <Value>Test Secret 2</Value>
431
+ </String>
432
+ <String>
433
+ <Key>UserName</Key>
434
+ <Value>Test User Name 2</Value>
435
+ </String>
436
+ </Entry>
437
+ </Group>
438
+ </Root>
439
+ </KeePassFile>
440
+ EO_XML
441
+ }
442
+ ) do
443
+ expect(test_services_handler).to receive(:package).with(
444
+ services: { 'node1' => %w[service1], 'node2' => %w[service2] },
445
+ secrets: {
446
+ 'Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' },
447
+ 'Test Secret 2' => { 'password' => 'TestPassword2', 'user_name' => 'Test User Name 2' }
448
+ },
449
+ local_environment: false
450
+ ) { raise 'Abort as testing secrets is enough' }
451
+ expect { test_deployer.deploy_on(%w[node1 node2]) }.to raise_error 'Abort as testing secrets is enough'
452
+ end
453
+ end
454
+
455
+ it 'gets secrets from a group path in a KeePass database' do
456
+ with_test_platform_for_keepass_test(
457
+ <<~EO_CONFIG,
458
+ use_kpscript_from '/path/to/kpscript'
459
+ secrets_from_keepass(
460
+ database: '/path/to/database.kdbx',
461
+ group_path: %w[Group1 Group2 Group3]
462
+ )
463
+ EO_CONFIG
464
+ expect_kpscript_calls: false
465
+ ) do
466
+ expect_calls_to_kpscript [
467
+ [
468
+ %r{/path/to/kpscript "/path/to/database.kdbx" -pw:"test_keepass_password" -c:Export -Format:"KeePass XML \(2.x\)" -OutFile:"/tmp/.+" -GroupPath:"Group1/Group2/Group3"},
469
+ {
470
+ stdout: 'OK: Operation completed successfully.',
471
+ xml: xml_single_entry
472
+ }
473
+ ]
474
+ ]
475
+ expect_secrets_to_be('Test Secret' => { 'password' => 'TestPassword', 'user_name' => 'Test User Name' })
476
+ end
477
+ end
478
+
479
+ it 'merges secrets from several KeePass databases' do
480
+ with_test_platform_for_keepass_test(
481
+ <<~EO_CONFIG,
482
+ use_kpscript_from '/path/to/kpscript'
483
+ secrets_from_keepass(database: '/path/to/database1.kdbx')
484
+ for_nodes('node2') do
485
+ secrets_from_keepass(database: '/path/to/database2.kdbx')
486
+ end
487
+ EO_CONFIG
488
+ platform_info: {
489
+ nodes: { 'node1' => { services: %w[service1] }, 'node2' => { services: %w[service2] } },
490
+ deployable_services: %w[service1 service2]
491
+ },
492
+ mock_databases: {
493
+ '/path/to/database1.kdbx' => <<~EO_XML,
494
+ <KeePassFile>
495
+ <Root>
496
+ <Group>
497
+ <Entry>
498
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
499
+ <String>
500
+ <Key>Password</Key>
501
+ <Value ProtectInMemory="True">TestPassword1</Value>
502
+ </String>
503
+ <String>
504
+ <Key>Title</Key>
505
+ <Value>Test Secret 1</Value>
506
+ </String>
507
+ <String>
508
+ <Key>UserName</Key>
509
+ <Value>Test User Name 1</Value>
510
+ </String>
511
+ </Entry>
512
+ <Group>
513
+ <UUID>RsonCc3VHk+k85z5zHhZzQ==</UUID>
514
+ <Name>Group1</Name>
515
+ <Entry>
516
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
517
+ <String>
518
+ <Key>Password</Key>
519
+ <Value ProtectInMemory="True">TestPassword3</Value>
520
+ </String>
521
+ <String>
522
+ <Key>Title</Key>
523
+ <Value>Test Secret 3</Value>
524
+ </String>
525
+ <String>
526
+ <Key>UserName</Key>
527
+ <Value>Test User Name 3</Value>
528
+ </String>
529
+ </Entry>
530
+ </Group>
531
+ </Group>
532
+ </Root>
533
+ </KeePassFile>
534
+ EO_XML
535
+ '/path/to/database2.kdbx' => <<~EO_XML
536
+ <KeePassFile>
537
+ <Root>
538
+ <Group>
539
+ <Entry>
540
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
541
+ <String>
542
+ <Key>Password</Key>
543
+ <Value ProtectInMemory="True">TestPassword2</Value>
544
+ </String>
545
+ <String>
546
+ <Key>Title</Key>
547
+ <Value>Test Secret 2</Value>
548
+ </String>
549
+ <String>
550
+ <Key>UserName</Key>
551
+ <Value>Test User Name 2</Value>
552
+ </String>
553
+ </Entry>
554
+ <Group>
555
+ <UUID>RsonCc3VHk+k85z5zHhZzQ==</UUID>
556
+ <Name>Group1</Name>
557
+ <Entry>
558
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
559
+ <String>
560
+ <Key>Password</Key>
561
+ <Value ProtectInMemory="True">TestPassword3</Value>
562
+ </String>
563
+ <String>
564
+ <Key>Title</Key>
565
+ <Value>Test Secret 3</Value>
566
+ </String>
567
+ <String>
568
+ <Key>Notes</Key>
569
+ <Value>Notes 3</Value>
570
+ </String>
571
+ </Entry>
572
+ <Entry>
573
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
574
+ <String>
575
+ <Key>Password</Key>
576
+ <Value ProtectInMemory="True">TestPassword4</Value>
577
+ </String>
578
+ <String>
579
+ <Key>Title</Key>
580
+ <Value>Test Secret 4</Value>
581
+ </String>
582
+ <String>
583
+ <Key>UserName</Key>
584
+ <Value>Test User Name 4</Value>
585
+ </String>
586
+ </Entry>
587
+ </Group>
588
+ </Group>
589
+ </Root>
590
+ </KeePassFile>
591
+ EO_XML
592
+ }
593
+ ) do
594
+ expect(test_services_handler).to receive(:package).with(
595
+ services: { 'node1' => %w[service1], 'node2' => %w[service2] },
596
+ secrets: {
597
+ 'Test Secret 1' => { 'password' => 'TestPassword1', 'user_name' => 'Test User Name 1' },
598
+ 'Test Secret 2' => { 'password' => 'TestPassword2', 'user_name' => 'Test User Name 2' },
599
+ 'Group1' => {
600
+ 'Test Secret 3' => { 'password' => 'TestPassword3', 'user_name' => 'Test User Name 3', 'notes' => 'Notes 3' },
601
+ 'Test Secret 4' => { 'password' => 'TestPassword4', 'user_name' => 'Test User Name 4' }
602
+ }
603
+ },
604
+ local_environment: false
605
+ ) { raise 'Abort as testing secrets is enough' }
606
+ expect { test_deployer.deploy_on(%w[node1 node2]) }.to raise_error 'Abort as testing secrets is enough'
607
+ end
608
+ end
609
+
610
+ it 'fails in case of secrets conflicts between several KeePass databases' do
611
+ with_test_platform_for_keepass_test(
612
+ <<~EO_CONFIG,
613
+ use_kpscript_from '/path/to/kpscript'
614
+ secrets_from_keepass(database: '/path/to/database1.kdbx')
615
+ for_nodes('node2') do
616
+ secrets_from_keepass(database: '/path/to/database2.kdbx')
617
+ end
618
+ EO_CONFIG
619
+ platform_info: {
620
+ nodes: { 'node1' => { services: %w[service1] }, 'node2' => { services: %w[service2] } },
621
+ deployable_services: %w[service1 service2]
622
+ },
623
+ mock_databases: {
624
+ '/path/to/database1.kdbx' => <<~EO_XML,
625
+ <KeePassFile>
626
+ <Root>
627
+ <Group>
628
+ <Entry>
629
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
630
+ <String>
631
+ <Key>Password</Key>
632
+ <Value ProtectInMemory="True">TestPassword1</Value>
633
+ </String>
634
+ <String>
635
+ <Key>Title</Key>
636
+ <Value>Test Secret 1</Value>
637
+ </String>
638
+ <String>
639
+ <Key>UserName</Key>
640
+ <Value>Test User Name 1</Value>
641
+ </String>
642
+ </Entry>
643
+ </Group>
644
+ </Root>
645
+ </KeePassFile>
646
+ EO_XML
647
+ '/path/to/database2.kdbx' => <<~EO_XML
648
+ <KeePassFile>
649
+ <Root>
650
+ <Group>
651
+ <Entry>
652
+ <UUID>Iv3JjMzpPEaijOB+SFZpRw==</UUID>
653
+ <String>
654
+ <Key>Password</Key>
655
+ <Value ProtectInMemory="True">OtherTestPassword1</Value>
656
+ </String>
657
+ <String>
658
+ <Key>Title</Key>
659
+ <Value>Test Secret 1</Value>
660
+ </String>
661
+ <String>
662
+ <Key>UserName</Key>
663
+ <Value>Test User Name 1</Value>
664
+ </String>
665
+ </Entry>
666
+ </Group>
667
+ </Root>
668
+ </KeePassFile>
669
+ EO_XML
670
+ }
671
+ ) do
672
+ expect { test_deployer.deploy_on(%w[node1 node2]) }.to raise_error 'Secret set at path Test Secret 1->password by /path/to/database2.kdbx for service service2 on node node2 has conflicting values (set debug for value details).'
673
+ end
674
+ end
675
+
676
+ end
677
+
678
+ end
679
+
680
+ end