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
@@ -11,10 +11,27 @@ module Bundler
11
11
  # @return [Hash] Environment with all bundler-related variables removed
12
12
  def current_unbundled_env
13
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
14
25
 
15
26
  env['MANPATH'] = env['BUNDLER_ORIG_MANPATH'] if env.key?('BUNDLER_ORIG_MANPATH')
16
27
 
17
- env.delete_if { |k, _| k[0, 7] == 'BUNDLE_' }
28
+ env.delete_if do |k, _|
29
+ %w[
30
+ GEM_
31
+ BUNDLE_
32
+ BUNDLER_
33
+ ].any? { |prefix| k.start_with?(prefix) }
34
+ end
18
35
 
19
36
  if env.key?('RUBYOPT')
20
37
  rubyopt = env['RUBYOPT'].split
@@ -1,4 +1,5 @@
1
1
  require 'netrc'
2
+ require 'secret_string'
2
3
  require 'uri'
3
4
  require 'hybrid_platforms_conductor/logger_helpers'
4
5
 
@@ -7,122 +8,146 @@ module HybridPlatformsConductor
7
8
  # Give a secured and harmonized way to access credentials for a given service.
8
9
  # It makes sure to remove passwords from memory for hardened security (this way if a vulnerability allows an attacker to dump the memory it won't get passwords).
9
10
  # It gets credentials from the following sources:
11
+ # * Configuration
10
12
  # * Environment variables
11
13
  # * Netrc file
12
- class Credentials
14
+ module Credentials
13
15
 
14
- include LoggerHelpers
16
+ # Extend the Config DSL
17
+ module ConfigDSLExtension
18
+
19
+ # List of credentials. Each info has the following properties:
20
+ # * *credential_id* (Symbol): Credential ID this rule applies to
21
+ # * *resource* (Regexp): Resource filtering for this rule
22
+ # * *provider* (Proc): The code providing the credentials:
23
+ # * Parameters::
24
+ # * *resource* (String or nil): The resource for which we want credentials, or nil if none
25
+ # * *requester* (Proc): Code to be called to give credentials to:
26
+ # * Parameters::
27
+ # * *user* (String or nil): The user name, or nil if none
28
+ # * *password* (String or nil): The password, or nil if none
29
+ attr_reader :credentials
30
+
31
+ # Mixin initializer
32
+ def init_credentials_config
33
+ @credentials = []
34
+ end
35
+
36
+ # Define a credentials provider
37
+ #
38
+ # Parameters::
39
+ # * *credential_id* (Symbol): Credential ID this rule applies to
40
+ # * *resource* (String or Regexp): Resource filtering for this rule [default: /.*/]
41
+ # * *provider* (Proc): The code providing the credentials:
42
+ # * Parameters::
43
+ # * *resource* (String or nil): The resource for which we want credentials, or nil if none
44
+ # * *requester* (Proc): Code to be called to give credentials to:
45
+ # * Parameters::
46
+ # * *user* (String or nil): The user name, or nil if none
47
+ # * *password* (String or nil): The password, or nil if none
48
+ def credentials_for(credential_id, resource: /.*/, &provider)
49
+ @credentials << {
50
+ credential_id: credential_id,
51
+ resource: resource.is_a?(String) ? /^#{Regexp.escape(resource)}$/ : resource,
52
+ provider: provider
53
+ }
54
+ end
55
+
56
+ end
57
+
58
+ Config.extend_config_dsl_with ConfigDSLExtension, :init_credentials_config
15
59
 
16
60
  # Get access to credentials and make sure they are wiped out from memory when client code ends.
17
61
  # To ensure password safety, never store the password in a scope beyond the client code's Proc.
18
62
  #
19
63
  # Parameters::
20
64
  # * *id* (Symbol): Credential ID
21
- # * *logger* (Logger): Logger to be used
22
- # * *logger_stderr* (Logger): Logger to be used for stderr
23
- # * *url* (String or nil): The URL for which we want the credentials, or nil if not associated to a URL [default: nil]
65
+ # * *resource* (String or nil): The resource for which we want the credentials, or nil if not associated to a resource [default: nil]
24
66
  # * Proc: Client code called with credentials provided
25
67
  # * Parameters::
26
68
  # * *user* (String or nil): User name, or nil if none
27
- # * *password* (String or nil): Password, or nil if none.
28
- # !!! Never store this password in a scope broader than the client code itself !!!
29
- def self.with_credentials_for(id, logger, logger_stderr, url: nil)
30
- credentials = Credentials.new(id, url: url, logger: logger, logger_stderr: logger_stderr)
31
- begin
32
- yield credentials.user, credentials.password
33
- ensure
34
- credentials.clear_password
35
- end
36
- end
69
+ # * *password* (SecretString or nil): Password, or nil if none.
70
+ # !!! Never clone this password in a scope broader than the client code itself !!!
71
+ def with_credentials_for(id, resource: nil)
72
+ # Get the credentials provider
73
+ provider = nil
37
74
 
38
- # Constructor
39
- #
40
- # Parameters::
41
- # * *id* (Symbol): Credential ID
42
- # * *url* (String or nil): The URL for which we want the credentials, or nil if not associated to a URL [default: nil]
43
- # * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
44
- # * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
45
- def initialize(id, url: nil, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
46
- init_loggers(logger, logger_stderr)
47
- @id = id
48
- @url = url
49
- @user = nil
50
- @password = nil
51
- @retrieved = false
52
- end
53
-
54
- # Provide a helper to clear password from memory for security.
55
- # To be used when the client knows it won't use the password anymore.
56
- def clear_password
57
- @password&.replace('gotyou!' * 100)
58
- GC.start
59
- end
60
-
61
- # Get the associated user
62
- #
63
- # Result::
64
- # * String or nil: The user name, or nil if none
65
- def user
66
- retrieve_credentials
67
- @user
68
- end
69
-
70
- # Get the associated password
71
- #
72
- # Result::
73
- # * String or nil: The password, or nil if none
74
- def password
75
- retrieve_credentials
76
- @password
77
- end
78
-
79
- private
80
-
81
- # Retrieve credentials in @user and @password.
82
- # Do it only once.
83
- # Make sure the retrieved credentials are not linked to other objects in memory, so that we can remove any other trace of secrets.
84
- def retrieve_credentials
85
- return if @retrieved
75
+ # Check configuration
76
+ # Take the last matching provider, this way we can define several providers for resources matched in a increasingly refined way.
77
+ @config.credentials.each do |credentials_info|
78
+ provider = credentials_info[:provider] if credentials_info[:credential_id] == id && (
79
+ (resource.nil? && credentials_info[:resource] == /.*/) || credentials_info[:resource] =~ resource
80
+ )
81
+ end
86
82
 
87
- # Check environment variables
88
- @user = ENV["hpc_user_for_#{@id}"].dup
89
- @password = ENV["hpc_password_for_#{@id}"].dup
90
- if @user.nil? || @user.empty? || @password.nil? || @password.empty?
91
- log_debug "[ Credentials for #{@id} ] - Credentials not found from environment variables."
92
- if @url.nil?
93
- log_debug "[ Credentials for #{@id} ] - No URL associated to this credentials, so .netrc can't be used."
94
- else
95
- # Check Netrc
96
- netrc = ::Netrc.read
97
- begin
98
- netrc_user, netrc_password = netrc[URI.parse(@url).host.downcase]
99
- if netrc_user.nil?
100
- log_debug "[ Credentials for #{@id} ] - No credentials retrieved from .netrc."
101
- # TODO: Add more credentials source if needed here
102
- log_warn "[ Credentials for #{@id} ] - Unable to get credentials for #{@id} (URL: #{@url})."
103
- else
104
- @user = netrc_user.dup
105
- @password = netrc_password.dup
106
- log_debug "[ Credentials for #{@id} ] - Credentials retrieved from .netrc using #{@url}."
107
- end
108
- ensure
109
- # Make sure the password does not stay in Netrc memory
110
- # Wipe out any memory trace that might contain passwords in clear
111
- netrc.instance_variable_get(:@data).each do |data_line|
112
- data_line.each do |data_string|
113
- data_string.replace('GotYou!!!' * 100)
83
+ provider ||= proc do |requested_resource, requester|
84
+ # Check environment variables
85
+ user = ENV["hpc_user_for_#{id}"]
86
+ # Clone the password as we are going to treat it as a secret string that will be wiped out
87
+ password = ENV["hpc_password_for_#{id}"].dup
88
+ if user.nil? || user.empty? || password.nil? || password.empty?
89
+ log_debug "[ Credentials for #{id} ] - Credentials not found from environment variables."
90
+ if requested_resource.nil?
91
+ log_debug "[ Credentials for #{id} ] - No resource associated to this credentials, so .netrc can't be used."
92
+ else
93
+ # Check Netrc
94
+ netrc = ::Netrc.read
95
+ begin
96
+ netrc_user, netrc_password = netrc[
97
+ begin
98
+ URI.parse(requested_resource).host.downcase
99
+ rescue URI::InvalidURIError
100
+ requested_resource
101
+ end
102
+ ]
103
+ if netrc_user.nil?
104
+ log_debug "[ Credentials for #{id} ] - No credentials retrieved from .netrc."
105
+ # TODO: Add more credentials source if needed here
106
+ log_warn "[ Credentials for #{id} ] - Unable to get credentials for #{id} (Resource: #{requested_resource})."
107
+ else
108
+ # Clone in memory as we are going to wipe out ::Netrc's memory
109
+ user = netrc_user.dup
110
+ password = netrc_password.dup
111
+ log_debug "[ Credentials for #{id} ] - Credentials retrieved from .netrc using #{requested_resource}."
112
+ end
113
+ ensure
114
+ # Make sure the password does not stay in Netrc memory
115
+ # Wipe out any memory trace that might contain passwords in clear
116
+ netrc.instance_variable_get(:@data).each do |data_line|
117
+ data_line.each do |data_string|
118
+ data_string.replace('GotYou!!!' * 100)
119
+ end
114
120
  end
115
121
  end
116
- # We don this assignment on purpose so that GC can remove sensitive data later
117
- # rubocop:disable Lint/UselessAssignment
118
- netrc = nil
119
- # rubocop:enable Lint/UselessAssignment
122
+ end
123
+ else
124
+ log_debug "[ Credentials for #{id} ] - Credentials retrieved from environment variables."
125
+ end
126
+ if password.nil?
127
+ requester.call user, password
128
+ else
129
+ SecretString.protect(password) do |secret_password|
130
+ requester.call user, secret_password
120
131
  end
121
132
  end
122
- else
123
- log_debug "[ Credentials for #{@id} ] - Credentials retrieved from environment variables."
124
133
  end
125
- GC.start
134
+
135
+ requester_called = false
136
+ provider.call(
137
+ resource,
138
+ proc do |user, password|
139
+ requester_called = true
140
+ if password.is_a?(String)
141
+ SecretString.protect(password) do |secret_password|
142
+ yield user, secret_password
143
+ end
144
+ else
145
+ yield user, password
146
+ end
147
+ end
148
+ )
149
+
150
+ raise "Requester not called by the credentials provider for #{id} (resource: #{resource}) - Please check the credentials_for code in your configuration." unless requester_called
126
151
  end
127
152
 
128
153
  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::
@@ -72,10 +106,10 @@ module HybridPlatformsConductor
72
106
 
73
107
  end
74
108
 
75
- include LoggerHelpers
76
-
77
109
  Config.extend_config_dsl_with ConfigDSLExtension, :init_deployer_config
78
110
 
111
+ include LoggerHelpers
112
+
79
113
  # Do we use why-run mode while deploying? [default = false]
80
114
  # Boolean
81
115
  attr_accessor :use_why_run
@@ -467,34 +501,7 @@ module HybridPlatformsConductor
467
501
 
468
502
  private
469
503
 
470
- # Safe-merge 2 hashes.
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).
@@ -0,0 +1,39 @@
1
+ require 'octokit'
2
+ require 'hybrid_platforms_conductor/credentials'
3
+
4
+ module HybridPlatformsConductor
5
+
6
+ # Mixin used to access Github API
7
+ module Github
8
+
9
+ include Credentials
10
+
11
+ # Iterate over each Github repository
12
+ #
13
+ # Parameters::
14
+ # * Proc: Code called for each Github repository:
15
+ # * Parameters::
16
+ # * *github* (Octokit::Client): The client instance accessing the Github API
17
+ # * *repo_info* (Hash<Symbol, Object>): The repository info:
18
+ # * *name* (String): Repository name.
19
+ # * *slug* (String): Repository slug.
20
+ def for_each_github_repo
21
+ @config.known_github_repos.each do |repo_info|
22
+ Octokit.configure do |c|
23
+ c.api_endpoint = repo_info[:url]
24
+ end
25
+ with_credentials_for(:github, resource: repo_info[:url]) do |_github_user, github_token|
26
+ client = Octokit::Client.new(access_token: github_token&.to_unprotected)
27
+ (repo_info[:repos] == :all ? client.repositories(repo_info[:user]).map { |repo| repo[:name] } : repo_info[:repos]).each do |name|
28
+ yield client, {
29
+ name: name,
30
+ slug: "#{repo_info[:user]}/#{name}"
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -14,7 +14,7 @@ module HybridPlatformsConductor
14
14
  # [API] - @actions_executor is accessible
15
15
  #
16
16
  # Parameters::
17
- # * *cmd* (String): The bash command to execute
17
+ # * *cmd* (String or SecretString): The bash command to execute
18
18
  def setup(cmd)
19
19
  @cmd = cmd
20
20
  end
@@ -15,30 +15,30 @@ module HybridPlatformsConductor
15
15
  #
16
16
  # Parameters::
17
17
  # * *remote_bash* (Array or Object): List of commands (or single command) to be executed. Each command can be the following:
18
- # * String: Simple bash command.
18
+ # * String or SecretString: Simple bash command.
19
19
  # * Hash<Symbol, Object>: Information about the commands to execute. Can have the following properties:
20
- # * *commands* (Array<String> or String): List of bash commands to execute (can be a single one) [default: ''].
20
+ # * *commands* (Array<String or SecretString> or String or SecretString): List of bash commands to execute (can be a single one) [default: ''].
21
21
  # * *file* (String): Name of file from which commands should be taken [optional].
22
- # * *env* (Hash<String, String>): Environment variables to be set before executing those commands [default: {}].
22
+ # * *env* (Hash<String, String or SecretString>): Environment variables to be set before executing those commands [default: {}].
23
23
  def setup(remote_bash)
24
24
  # Normalize the parameters.
25
25
  # Array< Hash<Symbol,Object> >: Simple array of info:
26
- # * *commands* (Array<String>): List of bash commands to execute.
27
- # * *env* (Hash<String, String>): Environment variables to be set before executing those commands.
26
+ # * *commands* (Array<String or SecretString>): List of bash commands to execute.
27
+ # * *env* (Hash<String, String or SecretString>): Environment variables to be set before executing those commands.
28
28
  @remote_bash = (remote_bash.is_a?(Array) ? remote_bash : [remote_bash]).map do |cmd_info|
29
- if cmd_info.is_a?(String)
30
- {
31
- commands: [cmd_info],
32
- env: {}
33
- }
34
- else
29
+ if cmd_info.is_a?(Hash)
35
30
  commands = []
36
- commands.concat(cmd_info[:commands].is_a?(String) ? [cmd_info[:commands]] : cmd_info[:commands]) if cmd_info[:commands]
31
+ commands.concat(cmd_info[:commands].is_a?(Array) ? cmd_info[:commands] : [cmd_info[:commands]]) if cmd_info[:commands]
37
32
  commands << File.read(cmd_info[:file]) if cmd_info[:file]
38
33
  {
39
34
  commands: commands,
40
35
  env: cmd_info[:env] || {}
41
36
  }
37
+ else
38
+ {
39
+ commands: [cmd_info],
40
+ env: {}
41
+ }
42
42
  end
43
43
  end
44
44
  end
@@ -63,11 +63,21 @@ module HybridPlatformsConductor
63
63
  # [API] - @stderr_io can be used to log stderr messages
64
64
  # [API] - run_cmd(String) method can be used to execute a command. See CmdRunner#run_cmd to know about the result's signature.
65
65
  def execute
66
- bash_str = @remote_bash.map do |cmd_info|
67
- (cmd_info[:env].map { |var_name, var_value| "export #{var_name}='#{var_value}'" } + cmd_info[:commands]).join("\n")
68
- end.join("\n")
69
- log_debug "[#{@node}] - Execute remote Bash commands \"#{bash_str}\"..."
70
- @connector.remote_bash bash_str
66
+ # The commands or ENV variables can contain secrets, so make sure to protect all strings from secrets leaking
67
+ bash_cmds = @remote_bash.map do |cmd_info|
68
+ cmd_info[:env].map do |var_name, var_value|
69
+ SecretString.new("export #{var_name}='#{var_value.to_unprotected}'", silenced_str: "export #{var_name}='#{var_value}'")
70
+ end + cmd_info[:commands]
71
+ end.flatten
72
+ begin
73
+ SecretString.protect(bash_cmds.map(&:to_unprotected).join("\n"), silenced_str: bash_cmds.join("\n")) do |bash_str|
74
+ log_debug "[#{@node}] - Execute remote Bash commands \"#{bash_str}\"..."
75
+ @connector.remote_bash bash_str
76
+ end
77
+ ensure
78
+ # Make sure we erase all secret strings
79
+ bash_cmds.each(&:erase)
80
+ end
71
81
  end
72
82
 
73
83
  end