hybrid_platforms_conductor 33.2.0 → 33.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/docs/plugins.md +1 -0
- data/docs/plugins/secrets_reader/keepass.md +62 -0
- data/lib/hybrid_platforms_conductor/cmd_runner.rb +1 -1
- data/lib/hybrid_platforms_conductor/config.rb +0 -35
- data/lib/hybrid_platforms_conductor/core_extensions/bundler/without_bundled_env.rb +54 -0
- data/lib/hybrid_platforms_conductor/deployer.rb +35 -28
- data/lib/hybrid_platforms_conductor/executable.rb +2 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +4 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/reserve_proxmox_container +1 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +173 -0
- data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
- data/lib/hybrid_platforms_conductor/safe_merge.rb +37 -0
- data/lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb +5 -3
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +4 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +8 -6
- data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +15 -1
- data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +48 -72
- data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +36 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +719 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs_plugins_api_spec.rb +2 -2
- data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +1 -1
- data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +21 -15
- metadata +159 -139
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0707386d30d5e671d5ceac5e1fec9d971cdccd2b7057c6ed3a75cb5d8bb6d85
|
4
|
+
data.tar.gz: e9e5c023662e7aa9283905f4ae54f4b8995d14f61bea1d844ef4fef32cba68dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b70dedf6605d48081ead37771b8f94e29d26334fb1c0f7f15ed8ed70cabc24809b145bd681af9e829cd0c968accd18cf87886987391709a7252908a00104096
|
7
|
+
data.tar.gz: cbe9033432aae4b83b4153501efad2330651696ba0fc8b487d21f5927430d481661e5d3bf2c3617ffb20c2ba7e254f82328d4225aeba4f98295873c55fe4cca9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,64 @@
|
|
1
|
+
# [v33.3.0](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.4...v33.3.0) (2021-07-02 17:20:58)
|
2
|
+
|
3
|
+
## Global changes
|
4
|
+
### Patches
|
5
|
+
|
6
|
+
* [[Feature(secrets_reader_keepass)] [#79] Add Keepass secrets reader plugin to get secrets from KeePass databases](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/7ace48653a74f03ed535ee6b41b21242a6454ff3)
|
7
|
+
|
8
|
+
## Changes for secrets_reader_keepass
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* [[Feature(secrets_reader_keepass)] [#79] Add Keepass secrets reader plugin to get secrets from KeePass databases](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/7ace48653a74f03ed535ee6b41b21242a6454ff3)
|
12
|
+
|
13
|
+
# [v33.2.4](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.3...v33.2.4) (2021-06-23 15:14:20)
|
14
|
+
|
15
|
+
## Global changes
|
16
|
+
### Patches
|
17
|
+
|
18
|
+
* [[Hotfix(platform_handler_serverless_chef)] Forward environment in sudo commands](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/fd3b58875665c29dd071b5af2055eab0c45c0974)
|
19
|
+
* [[Hotfix] Fixed unbundled environment not cleaned + Moved deployer config DSL in deployer.rb](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/2bce6dbc31d98b27f196ed646eb9aa669b0f9a86)
|
20
|
+
|
21
|
+
## Changes for platform_handler_serverless_chef
|
22
|
+
### Patches
|
23
|
+
|
24
|
+
* [[Hotfix(platform_handler_serverless_chef)] Forward environment in sudo commands](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/fd3b58875665c29dd071b5af2055eab0c45c0974)
|
25
|
+
|
26
|
+
# [v33.2.3](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.2...v33.2.3) (2021-06-23 13:45:56)
|
27
|
+
|
28
|
+
## Global changes
|
29
|
+
### Patches
|
30
|
+
|
31
|
+
* [[Hotfix(provisioner_proxmox)] Add missing require in synchronization script](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/7a6c71595789c9180e1d95323fc5cf5051f2e2cd)
|
32
|
+
|
33
|
+
## Changes for provisioner_proxmox
|
34
|
+
### Patches
|
35
|
+
|
36
|
+
* [[Hotfix(provisioner_proxmox)] Add missing require in synchronization script](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/7a6c71595789c9180e1d95323fc5cf5051f2e2cd)
|
37
|
+
|
38
|
+
# [v33.2.2](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.1...v33.2.2) (2021-06-21 12:41:35)
|
39
|
+
|
40
|
+
## Global changes
|
41
|
+
### Patches
|
42
|
+
|
43
|
+
* [[Hotfix(cmd_runner)] Retain dynamically set environment while executing commands](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/d709d5d2871e43196cc1f5f9eaf5b2155b34ed4e)
|
44
|
+
|
45
|
+
## Changes for cmd_runner
|
46
|
+
### Patches
|
47
|
+
|
48
|
+
* [[Hotfix(cmd_runner)] Retain dynamically set environment while executing commands](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/d709d5d2871e43196cc1f5f9eaf5b2155b34ed4e)
|
49
|
+
|
50
|
+
# [v33.2.1](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.0...v33.2.1) (2021-06-21 10:23:51)
|
51
|
+
|
52
|
+
## Global changes
|
53
|
+
### Patches
|
54
|
+
|
55
|
+
* [[Hotfix(platform_handler_serverless_chef)] Corrected dry-run mode not working](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/4800a0f4255c1999eed33651c1e66c445acd17bb)
|
56
|
+
|
57
|
+
## Changes for platform_handler_serverless_chef
|
58
|
+
### Patches
|
59
|
+
|
60
|
+
* [[Hotfix(platform_handler_serverless_chef)] Corrected dry-run mode not working](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/4800a0f4255c1999eed33651c1e66c445acd17bb)
|
61
|
+
|
1
62
|
# [v33.2.0](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.1.1...v33.2.0) (2021-06-18 23:22:21)
|
2
63
|
|
3
64
|
## Global changes
|
data/docs/plugins.md
CHANGED
@@ -196,6 +196,7 @@ Check the [sample plugin file](../lib/hybrid_platforms_conductor/hpc_plugins/sec
|
|
196
196
|
|
197
197
|
Plugins shipped by default:
|
198
198
|
* [`cli`](plugins/secrets_reader/cli.md)
|
199
|
+
* [`keepass`](plugins/secrets_reader/keepass.md)
|
199
200
|
* [`thycotic`](plugins/secrets_reader/thycotic.md)
|
200
201
|
|
201
202
|
<a name="test"></a>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Secrets reader plugin: `keepass`
|
2
|
+
|
3
|
+
The `keepass` secrets reader plugin retrieves secrets from [KeePass](https://keepass.info/) databases, using an actual KeePass installation with the [KPScript plugin](https://keepass.info/plugins.html#kpscript).
|
4
|
+
|
5
|
+
It is configured by giving the KPScript command-line (using `use_kpscript_from` config DSL method), the KeePass databases to be read (using `secrets_from_keepass` config DSL method) and uses the `keepass` credential ID to authenticate along with extra environment variables for eventual key files or encrypted passwords.
|
6
|
+
|
7
|
+
## Config DSL extension
|
8
|
+
|
9
|
+
### `use_kpscript_from`
|
10
|
+
|
11
|
+
Provide the KPScript command-line to be used. If KPScript is already in your path, using `KPScript.exe` or `kpscript` should be enough, otherwise the full path to the command-line will be needed. On Windows it is needed to also include double quotes if the path contains spaces (like `"C:\Program Files\KeePass\KPScript.exe"`).
|
12
|
+
|
13
|
+
It takes a simple `String` as parameter to get the command line.
|
14
|
+
|
15
|
+
Example:
|
16
|
+
```ruby
|
17
|
+
use_kpscript_from '/path/to/kpscript'
|
18
|
+
```
|
19
|
+
|
20
|
+
### `secrets_from_keepass`
|
21
|
+
|
22
|
+
Define a KeePass database to read secrets from.
|
23
|
+
A base group path of the KeePass database can also be specified to only read secrets from this group path.
|
24
|
+
|
25
|
+
All entries, attachments and sub-groups from the base group will be read as secrets.
|
26
|
+
|
27
|
+
Can be applied to subset of nodes using the [`for_nodes` DSL method](/docs/config_dsl.md#for_nodes).
|
28
|
+
|
29
|
+
It takes the following parameters:
|
30
|
+
* **database** (`String`): Database file path.
|
31
|
+
* **group_path** (`Array<String>`): Group path to extract from [default: `[]`].
|
32
|
+
|
33
|
+
Example:
|
34
|
+
```ruby
|
35
|
+
secrets_from_keepass(
|
36
|
+
database: '/path/to/database.kdbx',
|
37
|
+
group_path: %w[Secrets Automation]
|
38
|
+
)
|
39
|
+
```
|
40
|
+
|
41
|
+
## Used credentials
|
42
|
+
|
43
|
+
| Credential | Usage
|
44
|
+
| --- | --- |
|
45
|
+
| `keepass` | Used to get the password to the database. No need to be set if the database opens without password. |
|
46
|
+
|
47
|
+
## Used Metadata
|
48
|
+
|
49
|
+
| Metadata | Type | Usage
|
50
|
+
| --- | --- | --- |
|
51
|
+
|
52
|
+
## Used environment variables
|
53
|
+
|
54
|
+
| Variable | Usage
|
55
|
+
| --- | --- |
|
56
|
+
| `hpc_key_file_for_keepass` | Optional path to the key file needed to open the database |
|
57
|
+
| `hpc_password_enc_for_keepass` | Optional encrypted password needed to open the database |
|
58
|
+
|
59
|
+
## External tools dependencies
|
60
|
+
|
61
|
+
* [KeePass](https://keepass.info/) to open databases.
|
62
|
+
* [KPScript KeePass plugin](https://keepass.info/plugins.html#kpscript) to query KeePass API.
|
@@ -130,7 +130,7 @@ module HybridPlatformsConductor
|
|
130
130
|
(log_to_stdout ? [@logger_stderr] : []) +
|
131
131
|
(file_output.nil? ? [] : [file_output])
|
132
132
|
) do
|
133
|
-
Bundler.
|
133
|
+
Bundler.without_bundled_env do
|
134
134
|
cmd_result = TTY::Command.new(
|
135
135
|
printer: :null,
|
136
136
|
pty: true,
|
@@ -47,12 +47,6 @@ module HybridPlatformsConductor
|
|
47
47
|
# Array<Hash,Symbol,Object>
|
48
48
|
attr_reader :expected_failures
|
49
49
|
|
50
|
-
# List of retriable errors. Each info has the following properties:
|
51
|
-
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by those errors
|
52
|
-
# * *errors_on_stdout* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stdout
|
53
|
-
# * *errors_on_stderr* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stderr
|
54
|
-
attr_reader :retriable_errors
|
55
|
-
|
56
50
|
# List of deployment schedules. Each info has the following properties:
|
57
51
|
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule
|
58
52
|
# * *schedule* (IceCube::Schedule): The deployment schedule
|
@@ -81,11 +75,6 @@ module HybridPlatformsConductor
|
|
81
75
|
# * *reason* (String): Reason for this expected failure
|
82
76
|
# Array<Hash,Symbol,Object>
|
83
77
|
@expected_failures = []
|
84
|
-
# List of retriable errors. Each info has the following properties:
|
85
|
-
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by those errors
|
86
|
-
# * *errors_on_stdout* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stdout
|
87
|
-
# * *errors_on_stderr* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stderr
|
88
|
-
@retriable_errors = []
|
89
78
|
# List of deployment schedules. Each info has the following properties:
|
90
79
|
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule
|
91
80
|
# * *schedule* (IceCube::Schedule): The deployment schedule
|
@@ -162,30 +151,6 @@ module HybridPlatformsConductor
|
|
162
151
|
end
|
163
152
|
expose :expect_tests_to_fail
|
164
153
|
|
165
|
-
# Mark some errors on stdout to be retriable during a deploy
|
166
|
-
#
|
167
|
-
# Parameters::
|
168
|
-
# * *errors* (String, Regexp or Array<String or Regexp>): Single (or list of) errors matching pattern (either as exact string match or using a regexp).
|
169
|
-
def retry_deploy_for_errors_on_stdout(errors)
|
170
|
-
@retriable_errors << {
|
171
|
-
errors_on_stdout: errors.is_a?(Array) ? errors : [errors],
|
172
|
-
nodes_selectors_stack: current_nodes_selectors_stack
|
173
|
-
}
|
174
|
-
end
|
175
|
-
expose :retry_deploy_for_errors_on_stdout
|
176
|
-
|
177
|
-
# Mark some errors on stderr to be retriable during a deploy
|
178
|
-
#
|
179
|
-
# Parameters::
|
180
|
-
# * *errors* (String, Regexp or Array<String or Regexp>): Single (or list of) errors matching pattern (either as exact string match or using a regexp).
|
181
|
-
def retry_deploy_for_errors_on_stderr(errors)
|
182
|
-
@retriable_errors << {
|
183
|
-
errors_on_stderr: errors.is_a?(Array) ? errors : [errors],
|
184
|
-
nodes_selectors_stack: current_nodes_selectors_stack
|
185
|
-
}
|
186
|
-
end
|
187
|
-
expose :retry_deploy_for_errors_on_stderr
|
188
|
-
|
189
154
|
# Set a deployment schedule
|
190
155
|
#
|
191
156
|
# Parameters::
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Add a way to clean the current env from Bundler variables
|
2
|
+
module Bundler
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# Run block with all bundler-related variables removed from the current environment
|
7
|
+
def without_bundled_env(&block)
|
8
|
+
with_env(current_unbundled_env, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Hash] Environment with all bundler-related variables removed
|
12
|
+
def current_unbundled_env
|
13
|
+
env = ENV.clone.to_hash
|
14
|
+
%w[
|
15
|
+
PATH
|
16
|
+
RUBYLIB
|
17
|
+
RUBYOPT
|
18
|
+
].each do |env_name|
|
19
|
+
if original_env.key?(env_name)
|
20
|
+
env[env_name] = original_env[env_name]
|
21
|
+
else
|
22
|
+
env.delete(env_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
env['MANPATH'] = env['BUNDLER_ORIG_MANPATH'] if env.key?('BUNDLER_ORIG_MANPATH')
|
27
|
+
|
28
|
+
env.delete_if do |k, _|
|
29
|
+
%w[
|
30
|
+
GEM_
|
31
|
+
BUNDLE_
|
32
|
+
BUNDLER_
|
33
|
+
].any? { |prefix| k.start_with?(prefix) }
|
34
|
+
end
|
35
|
+
|
36
|
+
if env.key?('RUBYOPT')
|
37
|
+
rubyopt = env['RUBYOPT'].split
|
38
|
+
rubyopt.delete("-r#{File.expand_path('bundler/setup', __dir__)}")
|
39
|
+
rubyopt.delete('-rbundler/setup')
|
40
|
+
env['RUBYOPT'] = rubyopt.join(' ')
|
41
|
+
end
|
42
|
+
|
43
|
+
if env.key?('RUBYLIB')
|
44
|
+
rubylib = env['RUBYLIB'].split(File::PATH_SEPARATOR)
|
45
|
+
rubylib.delete(File.expand_path(__dir__))
|
46
|
+
env['RUBYLIB'] = rubylib.join(File::PATH_SEPARATOR)
|
47
|
+
end
|
48
|
+
|
49
|
+
env
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -9,6 +9,7 @@ require 'hybrid_platforms_conductor/logger_helpers'
|
|
9
9
|
require 'hybrid_platforms_conductor/nodes_handler'
|
10
10
|
require 'hybrid_platforms_conductor/services_handler'
|
11
11
|
require 'hybrid_platforms_conductor/plugins'
|
12
|
+
require 'hybrid_platforms_conductor/safe_merge'
|
12
13
|
|
13
14
|
module HybridPlatformsConductor
|
14
15
|
|
@@ -18,6 +19,12 @@ module HybridPlatformsConductor
|
|
18
19
|
# Extend the Config DSL
|
19
20
|
module ConfigDSLExtension
|
20
21
|
|
22
|
+
# List of retriable errors. Each info has the following properties:
|
23
|
+
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by those errors
|
24
|
+
# * *errors_on_stdout* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stdout
|
25
|
+
# * *errors_on_stderr* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stderr
|
26
|
+
attr_reader :retriable_errors
|
27
|
+
|
21
28
|
# List of log plugins. Each info has the following properties:
|
22
29
|
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
|
23
30
|
# * *log_plugins* (Array<Symbol>): List of log plugins to be used to store deployment logs.
|
@@ -36,6 +43,11 @@ module HybridPlatformsConductor
|
|
36
43
|
# Mixin initializer
|
37
44
|
def init_deployer_config
|
38
45
|
@packaging_timeout_secs = 60
|
46
|
+
# List of retriable errors. Each info has the following properties:
|
47
|
+
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by those errors
|
48
|
+
# * *errors_on_stdout* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stdout
|
49
|
+
# * *errors_on_stderr* (Array<String or Regexp>): List of errors match (as exact string match or using a regexp) to check against stderr
|
50
|
+
@retriable_errors = []
|
39
51
|
@deployment_logs = []
|
40
52
|
@secrets_readers = []
|
41
53
|
end
|
@@ -48,6 +60,28 @@ module HybridPlatformsConductor
|
|
48
60
|
@packaging_timeout_secs = packaging_timeout_secs
|
49
61
|
end
|
50
62
|
|
63
|
+
# Mark some errors on stdout to be retriable during a deploy
|
64
|
+
#
|
65
|
+
# Parameters::
|
66
|
+
# * *errors* (String, Regexp or Array<String or Regexp>): Single (or list of) errors matching pattern (either as exact string match or using a regexp).
|
67
|
+
def retry_deploy_for_errors_on_stdout(errors)
|
68
|
+
@retriable_errors << {
|
69
|
+
errors_on_stdout: errors.is_a?(Array) ? errors : [errors],
|
70
|
+
nodes_selectors_stack: current_nodes_selectors_stack
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Mark some errors on stderr to be retriable during a deploy
|
75
|
+
#
|
76
|
+
# Parameters::
|
77
|
+
# * *errors* (String, Regexp or Array<String or Regexp>): Single (or list of) errors matching pattern (either as exact string match or using a regexp).
|
78
|
+
def retry_deploy_for_errors_on_stderr(errors)
|
79
|
+
@retriable_errors << {
|
80
|
+
errors_on_stderr: errors.is_a?(Array) ? errors : [errors],
|
81
|
+
nodes_selectors_stack: current_nodes_selectors_stack
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
51
85
|
# Set the deployment log plugins to be used
|
52
86
|
#
|
53
87
|
# Parameters::
|
@@ -467,34 +501,7 @@ module HybridPlatformsConductor
|
|
467
501
|
|
468
502
|
private
|
469
503
|
|
470
|
-
|
471
|
-
# Safe-merging is done by:
|
472
|
-
# * Merging values that are hashes.
|
473
|
-
# * Reporting errors when values conflict.
|
474
|
-
# When values are conflicting, the initial hash won't modify those conflicting values and will stop the merge.
|
475
|
-
#
|
476
|
-
# Parameters::
|
477
|
-
# * *hash* (Hash): Hash to be modified merging hash_to_merge
|
478
|
-
# * *hash_to_merge* (Hash): Hash to be merged into hash
|
479
|
-
# Result::
|
480
|
-
# * nil or Array<Object>: nil in case of success, or the keys path leading to a conflicting value in case of error
|
481
|
-
def safe_merge(hash, hash_to_merge)
|
482
|
-
conflicting_path = nil
|
483
|
-
hash_to_merge.each do |key, value_to_merge|
|
484
|
-
if hash.key?(key)
|
485
|
-
if hash[key].is_a?(Hash) && value_to_merge.is_a?(Hash)
|
486
|
-
sub_conflicting_path = safe_merge(hash[key], value_to_merge)
|
487
|
-
conflicting_path = [key] + sub_conflicting_path unless sub_conflicting_path.nil?
|
488
|
-
elsif hash[key] != value_to_merge
|
489
|
-
conflicting_path = [key]
|
490
|
-
end
|
491
|
-
else
|
492
|
-
hash[key] = value_to_merge
|
493
|
-
end
|
494
|
-
break unless conflicting_path.nil?
|
495
|
-
end
|
496
|
-
conflicting_path
|
497
|
-
end
|
504
|
+
include SafeMerge
|
498
505
|
|
499
506
|
# Get the list of retriable errors a node got from deployment logs.
|
500
507
|
# Useful to know if an error is non-deterministic (due to external and temporary factors).
|
@@ -227,13 +227,14 @@ module HybridPlatformsConductor
|
|
227
227
|
# * Array< Hash<Symbol,Object> >: List of actions to be done
|
228
228
|
def actions_to_deploy_on(node, service, use_why_run: true)
|
229
229
|
package_dir = "#{@repository_path}/dist/#{@local_env ? 'local' : 'prod'}/#{service}"
|
230
|
+
gems_to_install = []
|
230
231
|
# Generate the nodes attributes file
|
231
232
|
unless @cmd_runner.dry_run
|
232
233
|
FileUtils.mkdir_p "#{package_dir}/nodes"
|
233
234
|
File.write("#{package_dir}/nodes/#{node}.json", (known_nodes.include?(node) ? metadata_for(node) : {}).merge(@nodes_handler.metadata_of(node)).to_json)
|
235
|
+
# Get the gems to be installed
|
236
|
+
gems_to_install = JSON.parse(File.read("#{package_dir}/gems.json"))
|
234
237
|
end
|
235
|
-
# Get the gems to be installed
|
236
|
-
gems_to_install = JSON.parse(File.read("#{package_dir}/gems.json"))
|
237
238
|
client_options = [
|
238
239
|
'--local-mode',
|
239
240
|
'--chef-license', 'accept',
|
@@ -262,7 +263,7 @@ module HybridPlatformsConductor
|
|
262
263
|
raise "Missing file #{chef_versions_file} specifying the Chef Infra Client version to be deployed" unless File.exist?(chef_versions_file)
|
263
264
|
|
264
265
|
required_chef_client_version = YAML.load_file(chef_versions_file)['client']
|
265
|
-
sudo = (@actions_executor.connector(:ssh).ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} ")
|
266
|
+
sudo = (@actions_executor.connector(:ssh).ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} -E ")
|
266
267
|
[
|
267
268
|
{
|
268
269
|
# Install dependencies
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'keepass_kpscript'
|
5
|
+
require 'zlib'
|
6
|
+
require 'hybrid_platforms_conductor/credentials'
|
7
|
+
require 'hybrid_platforms_conductor/safe_merge'
|
8
|
+
require 'hybrid_platforms_conductor/secrets_reader'
|
9
|
+
|
10
|
+
module HybridPlatformsConductor
|
11
|
+
|
12
|
+
module HpcPlugins
|
13
|
+
|
14
|
+
module SecretsReader
|
15
|
+
|
16
|
+
# Get secrets from a KeePass database
|
17
|
+
class Keepass < HybridPlatformsConductor::SecretsReader
|
18
|
+
|
19
|
+
include SafeMerge
|
20
|
+
|
21
|
+
# Extend the Config DSL
|
22
|
+
module ConfigDSLExtension
|
23
|
+
|
24
|
+
# List of defined KeePass secrets. Each info has the following properties:
|
25
|
+
# * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
|
26
|
+
# * *database* (String): Database file path.
|
27
|
+
# * *group_path* (Array<String>): Group path to extract from.
|
28
|
+
# Array< Hash<Symbol, Object> >
|
29
|
+
attr_reader :keepass_secrets
|
30
|
+
|
31
|
+
# String: The KPScript command line
|
32
|
+
attr_reader :kpscript
|
33
|
+
|
34
|
+
# Mixin initializer
|
35
|
+
def init_keepass_config
|
36
|
+
@keepass_secrets = []
|
37
|
+
@kpscript = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the KPScript command line
|
41
|
+
#
|
42
|
+
# Parameters::
|
43
|
+
# * *cmd* (String): KPScript command line
|
44
|
+
def use_kpscript_from(cmd)
|
45
|
+
@kpscript = cmd
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set a KeePass database configuration
|
49
|
+
#
|
50
|
+
# Parameters::
|
51
|
+
# * *database* (String): Database file path.
|
52
|
+
# * *group_path* (Array<String>): Group path to extract from [default: []].
|
53
|
+
def secrets_from_keepass(database:, group_path: [])
|
54
|
+
@keepass_secrets << {
|
55
|
+
nodes_selectors_stack: current_nodes_selectors_stack,
|
56
|
+
database: database,
|
57
|
+
group_path: group_path
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
Config.extend_config_dsl_with ConfigDSLExtension, :init_keepass_config
|
64
|
+
|
65
|
+
# Return secrets for a given service to be deployed on a node.
|
66
|
+
# [API] - This method is mandatory
|
67
|
+
# [API] - The following API components are accessible:
|
68
|
+
# * *@config* (Config): Main configuration API.
|
69
|
+
# * *@cmd_runner* (CmdRunner): Command Runner API.
|
70
|
+
# * *@nodes_handler* (NodesHandler): Nodes handler API.
|
71
|
+
#
|
72
|
+
# Parameters::
|
73
|
+
# * *node* (String): Node to be deployed
|
74
|
+
# * *service* (String): Service to be deployed
|
75
|
+
# Result::
|
76
|
+
# * Hash: The secrets
|
77
|
+
def secrets_for(node, service)
|
78
|
+
secrets = {}
|
79
|
+
# As we are dealing with global secrets, cache the reading for performance between nodes and services.
|
80
|
+
# Keep secrets cache grouped by URL/ID
|
81
|
+
@secrets = {} unless defined?(@secrets)
|
82
|
+
@nodes_handler.select_confs_for_node(node, @config.keepass_secrets).each do |keepass_secrets_info|
|
83
|
+
secret_id = "#{keepass_secrets_info[:database]}:#{keepass_secrets_info[:group_path].join('/')}"
|
84
|
+
unless @secrets.key?(secret_id)
|
85
|
+
raise 'Missing KPScript configuration. Please use use_kpscript_from to set it.' if @config.kpscript.nil?
|
86
|
+
|
87
|
+
Credentials.with_credentials_for(:keepass, @logger, @logger_stderr) do |_user, password|
|
88
|
+
Tempfile.create('hpc_keepass') do |xml_file|
|
89
|
+
key_file = ENV['hpc_key_file_for_keepass']
|
90
|
+
password_enc = ENV['hpc_password_enc_for_keepass']
|
91
|
+
keepass_credentials = {}
|
92
|
+
keepass_credentials[:password] = password if password
|
93
|
+
keepass_credentials[:password_enc] = password_enc if password_enc
|
94
|
+
keepass_credentials[:key_file] = key_file if key_file
|
95
|
+
KeepassKpscript.
|
96
|
+
use(@config.kpscript, debug: log_debug?).
|
97
|
+
open(keepass_secrets_info[:database], **keepass_credentials).
|
98
|
+
export('KeePass XML (2.x)', xml_file.path, group_path: keepass_secrets_info[:group_path].empty? ? nil : keepass_secrets_info[:group_path])
|
99
|
+
@secrets[secret_id] = parse_xml_secrets(Nokogiri::XML(xml_file).at_xpath('KeePassFile/Root/Group'))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
conflicting_path = safe_merge(secrets, @secrets[secret_id])
|
104
|
+
raise "Secret set at path #{conflicting_path.join('->')} by #{keepass_secrets_info[:database]}#{keepass_secrets_info[:group_path].empty? ? '' : " from group #{keepass_secrets_info[:group_path].join('/')}"} for service #{service} on node #{node} has conflicting values (#{log_debug? ? "#{@secrets[secret_id].dig(*conflicting_path)} != #{secrets.dig(*conflicting_path)}" : 'set debug for value details'})." unless conflicting_path.nil?
|
105
|
+
end
|
106
|
+
secrets
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# List of fields to include in the secrets and their corresponding XML name
|
112
|
+
FIELDS = {
|
113
|
+
notes: 'Notes',
|
114
|
+
password: 'Password',
|
115
|
+
url: 'URL',
|
116
|
+
user_name: 'UserName'
|
117
|
+
}
|
118
|
+
|
119
|
+
# Parse XML secrets from a Nokogiri XML group node
|
120
|
+
#
|
121
|
+
# Parameters::
|
122
|
+
# * *group* (Nokogiri::XML::Element): The group to parse
|
123
|
+
# Result::
|
124
|
+
# * Hash: The JSON secrets parsed from this group
|
125
|
+
def parse_xml_secrets(group)
|
126
|
+
# Parse all entries
|
127
|
+
group.xpath('Entry').map do |entry|
|
128
|
+
[
|
129
|
+
entry.at_xpath('String/Key[contains(.,"Title")]/../Value').text,
|
130
|
+
FIELDS.map do |property, field|
|
131
|
+
value = entry.at_xpath("String/Key[contains(.,\"#{field}\")]/../Value")&.text
|
132
|
+
if value.nil? || value.empty?
|
133
|
+
nil
|
134
|
+
else
|
135
|
+
[
|
136
|
+
property.to_s,
|
137
|
+
value
|
138
|
+
]
|
139
|
+
end
|
140
|
+
end.compact.to_h.merge(
|
141
|
+
entry.xpath('Binary').map do |binary|
|
142
|
+
binary_meta = group.document.at_xpath("KeePassFile/Meta/Binaries/Binary[@ID=#{Integer(binary.xpath('Value').attr('Ref').value)}]")
|
143
|
+
binary_content = Base64.decode64(binary_meta.text)
|
144
|
+
if binary_meta.attr('Compressed') == 'True'
|
145
|
+
gz = Zlib::GzipReader.new(StringIO.new(binary_content))
|
146
|
+
binary_content = gz.read
|
147
|
+
gz.close
|
148
|
+
end
|
149
|
+
[
|
150
|
+
binary.xpath('Key').text,
|
151
|
+
binary_content
|
152
|
+
]
|
153
|
+
end.to_h
|
154
|
+
)
|
155
|
+
]
|
156
|
+
end.to_h.merge(
|
157
|
+
# Add children groups
|
158
|
+
group.xpath('Group').map do |sub_group|
|
159
|
+
[
|
160
|
+
sub_group.at_xpath('Name').text,
|
161
|
+
parse_xml_secrets(sub_group)
|
162
|
+
]
|
163
|
+
end.to_h
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|