smart_proxy_remote_execution_ssh 0.7.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ca390cdbb692725653079d4c547ee4f3e1603111ba4f591ca29dc0ea5040fe9
4
- data.tar.gz: e47e47c97c5ee39a50349abcb2128d964025565f08fdf4edc872661111e14b2e
3
+ metadata.gz: f28e2eb48888626dad82eb56e7a5309da15d584e6d4c53eaf648e5ddfc6f3d1e
4
+ data.tar.gz: a5b604d80264dc78b461e1792100ecd6fc9a4a71791b8df5b064bd80a32f6920
5
5
  SHA512:
6
- metadata.gz: c72ce769673fd6c0055e4d36d6cf049ed16c97e9ce7f34a6f3c2169681aea0c54c5079fcabb4493ec1f8e2d4fa477c5c253372a61bb53da4211406156535cd24
7
- data.tar.gz: a8c18703b55a9562707f7f9c7bb719d44c0d816c1d4f8b40beaefcfd7588251bddab5ab5345145ccd2e80300786bf066c53a6e2af72e4918019c011f73db3db9
6
+ metadata.gz: 3231a3d465b180d1b8b325fd9fc341c009b566452946ee56837f9f37e6502d7095d1a213bc5b4b13078b9d54e5bd592f5430aea502ecdee93edab15901440815
7
+ data.tar.gz: 62e77d3cf6d37c90b2e6e8e48ad8b2080186a50fd1d3633b38743a099e0897b9b1e4d4dafaf7b02b1855dcd51036d418333c156438c3b20e5fa42feac93ded17
@@ -5,6 +5,8 @@ require 'time'
5
5
  module Proxy::RemoteExecution::Ssh::Actions
6
6
  class PullScript < Proxy::Dynflow::Action::Runner
7
7
  JobDelivered = Class.new
8
+ PickupTimeout = Class.new
9
+ ResendNotification = Class.new
8
10
 
9
11
  execution_plan_hooks.use :cleanup, :on => :stopped
10
12
 
@@ -17,9 +19,20 @@ module Proxy::RemoteExecution::Ssh::Actions
17
19
  if event == JobDelivered
18
20
  output[:state] = :delivered
19
21
  suspend
22
+ elsif event == PickupTimeout
23
+ process_pickup_timeout
24
+ elsif event == ResendNotification
25
+ if input[:with_mqtt] && %w(ready_for_pickup notified).include?(output[:state])
26
+ schedule_mqtt_resend
27
+ mqtt_start(::Proxy::Dynflow::OtpManager.passwords[execution_plan_id])
28
+ end
29
+ suspend
20
30
  else
21
31
  super
22
32
  end
33
+ rescue => e
34
+ action_logger.error(e)
35
+ process_update(Proxy::Dynflow::Runner::Update.encode_exception('Proxy error', e))
23
36
  end
24
37
 
25
38
  def init_run
@@ -27,10 +40,15 @@ module Proxy::RemoteExecution::Ssh::Actions
27
40
  ::Proxy::Dynflow::OtpManager.generate_otp(execution_plan_id)
28
41
  end
29
42
 
30
- input[:job_uuid] = job_storage.store_job(host_name, execution_plan_id, run_step_id, input[:script].tr("\r", ''))
43
+ plan_event(PickupTimeout, input[:time_to_pickup], optional: true) if input[:time_to_pickup]
44
+
45
+ input[:job_uuid] = job_storage.store_job(host_name, execution_plan_id, run_step_id, input[:script].tr("\r", ''), effective_user: input[:effective_user])
31
46
  output[:state] = :ready_for_pickup
32
47
  output[:result] = []
33
- mqtt_start(otp_password) if input[:with_mqtt]
48
+ if input[:with_mqtt]
49
+ schedule_mqtt_resend
50
+ mqtt_start(otp_password)
51
+ end
34
52
  suspend
35
53
  end
36
54
 
@@ -103,6 +121,8 @@ module Proxy::RemoteExecution::Ssh::Actions
103
121
  'username': execution_plan_id,
104
122
  'password': otp_password,
105
123
  'return_url': "#{input[:proxy_url]}/ssh/jobs/#{input[:job_uuid]}/update",
124
+ 'version': 'v1',
125
+ 'effective_user': input[:effective_user]
106
126
  },
107
127
  )
108
128
  mqtt_notify payload
@@ -164,5 +184,17 @@ module Proxy::RemoteExecution::Ssh::Actions
164
184
  directive: 'foreman'
165
185
  }
166
186
  end
187
+
188
+ def process_pickup_timeout
189
+ if output[:state] != :delivered
190
+ raise "The job was not picked up in time"
191
+ else
192
+ suspend
193
+ end
194
+ end
195
+
196
+ def schedule_mqtt_resend
197
+ plan_event(ResendNotification, settings[:mqtt_resend_interval], optional: true)
198
+ end
167
199
  end
168
200
  end
@@ -65,6 +65,7 @@ module Proxy::RemoteExecution
65
65
 
66
66
  with_authorized_job(job_uuid) do |job_record|
67
67
  notify_job(job_record, Actions::PullScript::JobDelivered)
68
+ response.headers['X-Foreman-Effective-User'] = job_record[:effective_user]
68
69
  job_record[:job]
69
70
  end
70
71
  end
@@ -1,5 +1,6 @@
1
1
  require 'smart_proxy_remote_execution_ssh/net_ssh_compat'
2
2
  require 'forwardable'
3
+ require 'securerandom'
3
4
 
4
5
  module Proxy::RemoteExecution
5
6
  module Cockpit
@@ -164,9 +165,8 @@ module Proxy::RemoteExecution
164
165
  out_read, out_write = IO.pipe
165
166
  err_read, err_write = IO.pipe
166
167
 
167
- # Force the script runner to initialize its logger
168
- script_runner.logger
169
- pid = spawn(*script_runner.send(:get_args, command), :in => in_read, :out => out_write, :err => err_write)
168
+ connection.establish!
169
+ pid = spawn(*connection.command(command), :in => in_read, :out => out_write, :err => err_write)
170
170
  [in_read, out_write, err_write].each(&:close)
171
171
 
172
172
  send_start
@@ -176,6 +176,8 @@ module Proxy::RemoteExecution
176
176
  in_buf = MiniSSLBufferedSocket.new(in_write)
177
177
 
178
178
  inner_system_ssh_loop out_buf, err_buf, in_buf, pid
179
+ ensure
180
+ connection.disconnect!
179
181
  end
180
182
 
181
183
  def inner_system_ssh_loop(out_buf, err_buf, in_buf, pid)
@@ -189,7 +191,7 @@ module Proxy::RemoteExecution
189
191
 
190
192
  proxy_data(out_buf, in_buf)
191
193
  if buf_socket.closed?
192
- script_runner.close_session
194
+ connection.disconnect!
193
195
  end
194
196
 
195
197
  if out_buf.closed?
@@ -263,10 +265,10 @@ module Proxy::RemoteExecution
263
265
  params["hostname"]
264
266
  end
265
267
 
266
- def script_runner
267
- @script_runner ||= Proxy::RemoteExecution::Ssh::Runners::ScriptRunner.build(
268
+ def connection
269
+ @connection ||= Proxy::RemoteExecution::Ssh::Runners::MultiplexedSSHConnection.new(
268
270
  runner_params,
269
- suspended_action: nil
271
+ logger: logger
270
272
  )
271
273
  end
272
274
 
@@ -279,6 +281,7 @@ module Proxy::RemoteExecution
279
281
  # For compatibility only
280
282
  ret[:script] = nil
281
283
  ret[:hostname] = host
284
+ ret[:id] = SecureRandom.uuid
282
285
  ret
283
286
  end
284
287
  end
@@ -0,0 +1,23 @@
1
+ # lib/command_logging.rb
2
+
3
+ module Proxy::RemoteExecution::Ssh::Runners
4
+ module CommandLogging
5
+ def log_command(command, label: "Running")
6
+ command = command.join(' ')
7
+ label = "#{label}: " if label
8
+ logger.debug("#{label}#{command}")
9
+ end
10
+
11
+ def set_pm_debug_logging(pm, capture: false, user_method: nil)
12
+ callback = proc do |data|
13
+ data.each_line do |line|
14
+ logger.debug(line.chomp) if user_method.nil? || !user_method.filter_password?(line)
15
+ user_method.on_data(data, pm.stdin) if user_method
16
+ end
17
+ data
18
+ end
19
+ pm.on_stdout(&callback)
20
+ pm.on_stderr(&callback)
21
+ end
22
+ end
23
+ end
@@ -11,6 +11,7 @@ module Proxy::RemoteExecution::Ssh
11
11
  String :hostname, null: false, index: true
12
12
  String :execution_plan_uuid, fixed: true, size: 36, null: false, index: true
13
13
  Integer :run_step_id, null: false
14
+ String :effective_user
14
15
  String :job, text: true
15
16
  end
16
17
  end
@@ -24,13 +25,14 @@ module Proxy::RemoteExecution::Ssh
24
25
  .select_map(:uuid)
25
26
  end
26
27
 
27
- def store_job(hostname, execution_plan_uuid, run_step_id, job, uuid: SecureRandom.uuid, timestamp: Time.now.utc)
28
+ def store_job(hostname, execution_plan_uuid, run_step_id, job, uuid: SecureRandom.uuid, timestamp: Time.now.utc, effective_user: nil)
28
29
  jobs.insert(timestamp: timestamp,
29
30
  uuid: uuid,
30
31
  hostname: hostname,
31
32
  execution_plan_uuid: execution_plan_uuid,
32
33
  run_step_id: run_step_id,
33
- job: job)
34
+ job: job,
35
+ effective_user: effective_user)
34
36
  uuid
35
37
  end
36
38
 
@@ -0,0 +1,196 @@
1
+ require 'smart_proxy_remote_execution_ssh/command_logging'
2
+
3
+ module Proxy::RemoteExecution::Ssh::Runners
4
+ class SensitiveString
5
+ def initialize(value, mask: '*****')
6
+ @value = value
7
+ @mask = mask
8
+ end
9
+
10
+ def inspect
11
+ '"' + to_s + '"'
12
+ end
13
+
14
+ def to_s
15
+ @mask
16
+ end
17
+
18
+ def to_str
19
+ @value
20
+ end
21
+ end
22
+
23
+ class AuthenticationMethod
24
+ attr_reader :name
25
+ def initialize(name, prompt: nil, password: nil)
26
+ @name = name
27
+ @prompt = prompt
28
+ @password = password
29
+ end
30
+
31
+ def ssh_command_prefix
32
+ return [] unless @password
33
+
34
+ prompt = ['-P', @prompt] if @prompt
35
+ [{'SSHPASS' => SensitiveString.new(@password)}, 'sshpass', '-e', prompt].compact
36
+ end
37
+
38
+ def ssh_options
39
+ ["-o PreferredAuthentications=#{name}",
40
+ "-o NumberOfPasswordPrompts=#{@password ? 1 : 0}"]
41
+ end
42
+ end
43
+
44
+ class MultiplexedSSHConnection
45
+ include CommandLogging
46
+
47
+ attr_reader :logger
48
+ def initialize(options, logger:)
49
+ @logger = logger
50
+
51
+ @id = options.fetch(:id)
52
+ @host = options.fetch(:hostname)
53
+ @script = options.fetch(:script)
54
+ @ssh_user = options.fetch(:ssh_user, 'root')
55
+ @ssh_port = options.fetch(:ssh_port, 22)
56
+ @ssh_password = options.fetch(:secrets, {}).fetch(:ssh_password, nil)
57
+ @key_passphrase = options.fetch(:secrets, {}).fetch(:key_passphrase, nil)
58
+ @host_public_key = options.fetch(:host_public_key, nil)
59
+ @verify_host = options.fetch(:verify_host, nil)
60
+ @client_private_key_file = settings.ssh_identity_key_file
61
+
62
+ @local_working_dir = options.fetch(:local_working_dir, settings.local_working_dir)
63
+ @socket_working_dir = options.fetch(:socket_working_dir, settings.socket_working_dir)
64
+ @socket = nil
65
+ end
66
+
67
+ def establish!
68
+ @available_auth_methods ||= available_authentication_methods
69
+ method = @available_auth_methods.find do |method|
70
+ if try_auth_method(method)
71
+ @available_auth_methods.unshift(method).uniq!
72
+ true
73
+ end
74
+ end
75
+ method || raise("Could not establish connection to remote host using any available authentication method, tried #{@available_auth_methods.map(&:name).join(', ')}")
76
+ end
77
+
78
+ def disconnect!
79
+ return unless connected?
80
+
81
+ cmd = command(%w[-O exit])
82
+ log_command(cmd, label: "Closing shared connection")
83
+ pm = Proxy::Dynflow::ProcessManager.new(cmd)
84
+ set_pm_debug_logging(pm)
85
+ pm.run!
86
+ @socket = nil
87
+ end
88
+
89
+ def connected?
90
+ !@socket.nil?
91
+ end
92
+
93
+ def command(cmd)
94
+ raise "Cannot build command to run over multiplexed connection without having an established connection" unless connected?
95
+
96
+ ['ssh', reuse_ssh_options, cmd].flatten
97
+ end
98
+
99
+ private
100
+
101
+ def try_auth_method(method)
102
+ # running "ssh -f -N" instead of "ssh true" would be cleaner, but ssh
103
+ # does not close its stderr which trips up the process manager which
104
+ # expects all FDs to be closed
105
+
106
+ full_command = [method.ssh_command_prefix, 'ssh', establish_ssh_options, method.ssh_options, @host, 'true'].flatten
107
+ log_command(full_command)
108
+ pm = Proxy::Dynflow::ProcessManager.new(full_command)
109
+ pm.start!
110
+ if pm.status
111
+ raise pm.stderr.to_s
112
+ else
113
+ set_pm_debug_logging(pm)
114
+ pm.stdin.io.close
115
+ pm.run!
116
+ end
117
+
118
+ if pm.status.zero?
119
+ logger.debug("Established connection using authentication method #{method.name}")
120
+ @socket = socket_file
121
+ true
122
+ else
123
+ logger.debug("Failed to establish connection using authentication method #{method.name}")
124
+ false
125
+ end
126
+ end
127
+
128
+ def settings
129
+ Proxy::RemoteExecution::Ssh::Plugin.settings
130
+ end
131
+
132
+ def available_authentication_methods
133
+ methods = []
134
+ methods << AuthenticationMethod.new('password', password: @ssh_password) if @ssh_password
135
+ if verify_key_passphrase
136
+ methods << AuthenticationMethod.new('publickey', password: @key_passphrase, prompt: 'passphrase')
137
+ end
138
+ methods << AuthenticationMethod.new('gssapi-with-mic') if settings[:kerberos_auth]
139
+ raise "There are no available authentication methods" if methods.empty?
140
+ methods
141
+ end
142
+
143
+ def establish_ssh_options
144
+ return @establish_ssh_options if @establish_ssh_options
145
+ ssh_options = []
146
+ ssh_options << "-o User=#{@ssh_user}"
147
+ ssh_options << "-o Port=#{@ssh_port}" if @ssh_port
148
+ ssh_options << "-o IdentityFile=#{@client_private_key_file}" if @client_private_key_file
149
+ ssh_options << "-o IdentitiesOnly=yes"
150
+ ssh_options << "-o StrictHostKeyChecking=no"
151
+ ssh_options << "-o UserKnownHostsFile=#{prepare_known_hosts}" if @host_public_key
152
+ ssh_options << "-o LogLevel=#{ssh_log_level(true)}"
153
+ ssh_options << "-o ControlMaster=auto"
154
+ ssh_options << "-o ControlPath=#{socket_file}"
155
+ ssh_options << "-o ControlPersist=yes"
156
+ ssh_options << "-o ProxyCommand=none"
157
+ @establish_ssh_options = ssh_options
158
+ end
159
+
160
+ def reuse_ssh_options
161
+ ["-o", "ControlPath=#{@socket}", "-o", "LogLevel=#{ssh_log_level(false)}", @host]
162
+ end
163
+
164
+ def socket_file
165
+ File.join(@socket_working_dir, @id)
166
+ end
167
+
168
+ def verify_key_passphrase
169
+ command = ['ssh-keygen', '-y', '-f', File.expand_path(@client_private_key_file)]
170
+ log_command(command, label: "Checking if private key has passphrase")
171
+ pm = Proxy::Dynflow::ProcessManager.new(command)
172
+ pm.start!
173
+
174
+ raise pm.stderr.to_s if pm.status
175
+
176
+ pm.stdin.io.close
177
+ pm.run!
178
+
179
+ if pm.status.zero?
180
+ logger.debug("Private key is not protected with a passphrase")
181
+ @key_passphrase = nil
182
+ else
183
+ logger.debug("Private key is protected with a passphrase")
184
+ end
185
+
186
+ return true if pm.status.zero? || @key_passphrase
187
+
188
+ logger.debug("Private key is protected with a passphrase, but no passphrase was provided")
189
+ false
190
+ end
191
+
192
+ def ssh_log_level(new_connection)
193
+ new_connection ? settings[:ssh_log_level] : 'quiet'
194
+ end
195
+ end
196
+ end
@@ -25,7 +25,8 @@ module Proxy::RemoteExecution::Ssh
25
25
  # :mqtt_broker => nil,
26
26
  # :mqtt_port => nil,
27
27
  # :mqtt_tls => nil,
28
- :mode => :ssh
28
+ :mode => :ssh,
29
+ :mqtt_resend_interval => 900
29
30
 
30
31
  capability(proc { 'cockpit' if settings.cockpit_integration })
31
32
 
@@ -50,13 +50,13 @@ module Proxy::RemoteExecution::Ssh::Runners
50
50
  end
51
51
 
52
52
  def refresh
53
+ @connection.establish! unless @connection.connected?
53
54
  begin
54
55
  pm = run_sync("#{@user_method.cli_command_prefix} #{@retrieval_script}")
56
+ process_retrieved_data(pm.stdout.to_s.chomp, pm.stderr.to_s.chomp)
55
57
  rescue StandardError => e
56
58
  @logger.info("Error while connecting to the remote host on refresh: #{e.message}")
57
59
  end
58
-
59
- process_retrieved_data(pm.stdout.to_s.chomp, pm.stderr.to_s.chomp)
60
60
  ensure
61
61
  destroy_session
62
62
  end
@@ -138,7 +138,7 @@ module Proxy::RemoteExecution::Ssh::Runners
138
138
  end
139
139
 
140
140
  def destroy_session
141
- if @session
141
+ if @connection.connected?
142
142
  @logger.debug("Closing session with #{@ssh_user}@#{@host}")
143
143
  close_session
144
144
  end
@@ -1,6 +1,7 @@
1
1
  require 'fileutils'
2
2
  require 'smart_proxy_dynflow/runner/process_manager_command'
3
3
  require 'smart_proxy_dynflow/process_manager'
4
+ require 'smart_proxy_remote_execution_ssh/multiplexed_ssh_connection'
4
5
 
5
6
  module Proxy::RemoteExecution::Ssh::Runners
6
7
  class EffectiveUserMethod
@@ -92,6 +93,8 @@ module Proxy::RemoteExecution::Ssh::Runners
92
93
  # rubocop:disable Metrics/ClassLength
93
94
  class ScriptRunner < Proxy::Dynflow::Runner::Base
94
95
  include Proxy::Dynflow::Runner::ProcessManagerCommand
96
+ include CommandLogging
97
+
95
98
  attr_reader :execution_timeout_interval
96
99
 
97
100
  EXPECTED_POWER_ACTION_MESSAGES = ['restart host', 'shutdown host'].freeze
@@ -103,10 +106,7 @@ module Proxy::RemoteExecution::Ssh::Runners
103
106
  @script = options.fetch(:script)
104
107
  @ssh_user = options.fetch(:ssh_user, 'root')
105
108
  @ssh_port = options.fetch(:ssh_port, 22)
106
- @ssh_password = options.fetch(:secrets, {}).fetch(:ssh_password, nil)
107
- @key_passphrase = options.fetch(:secrets, {}).fetch(:key_passphrase, nil)
108
109
  @host_public_key = options.fetch(:host_public_key, nil)
109
- @verify_host = options.fetch(:verify_host, nil)
110
110
  @execution_timeout_interval = options.fetch(:execution_timeout_interval, nil)
111
111
 
112
112
  @client_private_key_file = settings.ssh_identity_key_file
@@ -116,6 +116,7 @@ module Proxy::RemoteExecution::Ssh::Runners
116
116
  @cleanup_working_dirs = options.fetch(:cleanup_working_dirs, settings.cleanup_working_dirs)
117
117
  @first_execution = options.fetch(:first_execution, false)
118
118
  @user_method = user_method
119
+ @options = options
119
120
  end
120
121
 
121
122
  def self.build(options, suspended_action:)
@@ -143,7 +144,9 @@ module Proxy::RemoteExecution::Ssh::Runners
143
144
 
144
145
  def start
145
146
  Proxy::RemoteExecution::Utils.prune_known_hosts!(@host, @ssh_port, logger) if @first_execution
146
- establish_connection
147
+ ensure_local_directory(@socket_working_dir)
148
+ @connection = MultiplexedSSHConnection.new(@options.merge(:id => @id), logger: logger)
149
+ @connection.establish!
147
150
  preflight_checks
148
151
  prepare_start
149
152
  script = initialization_script
@@ -172,16 +175,6 @@ module Proxy::RemoteExecution::Ssh::Runners
172
175
  end
173
176
  end
174
177
 
175
- def establish_connection
176
- # run_sync ['-f', '-N'] would be cleaner, but ssh does not close its
177
- # stderr which trips up the process manager which expects all FDs to be
178
- # closed
179
- ensure_remote_command(
180
- 'true',
181
- error: 'Failed to establish connection to remote host, exit code: %{exit_code}'
182
- )
183
- end
184
-
185
178
  def prepare_start
186
179
  @remote_script = cp_script_to_remote
187
180
  @output_path = File.join(File.dirname(@remote_script), 'output')
@@ -232,11 +225,7 @@ module Proxy::RemoteExecution::Ssh::Runners
232
225
  def close_session
233
226
  raise 'Control socket file does not exist' unless File.exist?(socket_file)
234
227
  @logger.debug("Sending exit request for session #{@ssh_user}@#{@host}")
235
- args = ['/usr/bin/ssh', @host, "-o", "ControlPath=#{socket_file}", "-O", "exit"].flatten
236
- pm = Proxy::Dynflow::ProcessManager.new(args)
237
- pm.on_stdout { |data| @logger.debug "[close_session]: #{data.chomp}"; data }
238
- pm.on_stderr { |data| @logger.debug "[close_session]: #{data.chomp}"; data }
239
- pm.run!
228
+ @connection.disconnect!
240
229
  end
241
230
 
242
231
  def close
@@ -264,35 +253,10 @@ module Proxy::RemoteExecution::Ssh::Runners
264
253
  @process_manager && @cleanup_working_dirs
265
254
  end
266
255
 
267
- def ssh_options(with_pty = false, quiet: false)
268
- ssh_options = []
269
- ssh_options << "-tt" if with_pty
270
- ssh_options << "-o User=#{@ssh_user}"
271
- ssh_options << "-o Port=#{@ssh_port}" if @ssh_port
272
- ssh_options << "-o IdentityFile=#{@client_private_key_file}" if @client_private_key_file
273
- ssh_options << "-o IdentitiesOnly=yes"
274
- ssh_options << "-o StrictHostKeyChecking=no"
275
- ssh_options << "-o PreferredAuthentications=#{available_authentication_methods.join(',')}"
276
- ssh_options << "-o UserKnownHostsFile=#{prepare_known_hosts}" if @host_public_key
277
- ssh_options << "-o NumberOfPasswordPrompts=1"
278
- ssh_options << "-o LogLevel=#{quiet ? 'quiet' : settings[:ssh_log_level]}"
279
- ssh_options << "-o ControlMaster=auto"
280
- ssh_options << "-o ControlPath=#{socket_file}"
281
- ssh_options << "-o ControlPersist=yes"
282
- ssh_options << "-o ProxyCommand=none"
283
- end
284
-
285
256
  def settings
286
257
  Proxy::RemoteExecution::Ssh::Plugin.settings
287
258
  end
288
259
 
289
- def get_args(command, with_pty = false, quiet: false)
290
- args = []
291
- args = [{'SSHPASS' => @key_passphrase}, '/usr/bin/sshpass', '-P', 'passphrase', '-e'] if @key_passphrase
292
- args = [{'SSHPASS' => @ssh_password}, '/usr/bin/sshpass', '-e'] if @ssh_password
293
- args += ['/usr/bin/ssh', @host, ssh_options(with_pty, quiet: quiet), command].flatten
294
- end
295
-
296
260
  # Initiates run of the remote command and yields the data when
297
261
  # available. The yielding doesn't happen automatically, but as
298
262
  # part of calling the `refresh` method.
@@ -300,7 +264,9 @@ module Proxy::RemoteExecution::Ssh::Runners
300
264
  raise 'Async command already in progress' if @process_manager&.started?
301
265
 
302
266
  @user_method.reset
303
- initialize_command(*get_args(command, true, quiet: true))
267
+ cmd = @connection.command([tty_flag(true), command].flatten.compact)
268
+ log_command(cmd)
269
+ initialize_command(*cmd)
304
270
 
305
271
  true
306
272
  end
@@ -309,17 +275,15 @@ module Proxy::RemoteExecution::Ssh::Runners
309
275
  @process_manager&.started? && @user_method.sent_all_data?
310
276
  end
311
277
 
278
+ def tty_flag(tty)
279
+ '-tt' if tty
280
+ end
281
+
312
282
  def run_sync(command, stdin: nil, close_stdin: true, tty: false, user_method: nil)
313
- pm = Proxy::Dynflow::ProcessManager.new(get_args(command, tty))
314
- callback = proc do |data|
315
- data.each_line do |line|
316
- logger.debug(line.chomp) if user_method.nil? || !user_method.filter_password?(line)
317
- user_method.on_data(data, pm.stdin) if user_method
318
- end
319
- data
320
- end
321
- pm.on_stdout(&callback)
322
- pm.on_stderr(&callback)
283
+ cmd = @connection.command([tty_flag(tty), command].flatten.compact)
284
+ log_command(cmd)
285
+ pm = Proxy::Dynflow::ProcessManager.new(cmd)
286
+ set_pm_debug_logging(pm, user_method: user_method)
323
287
  pm.start!
324
288
  unless pm.status
325
289
  pm.stdin.io.puts(stdin) if stdin
@@ -429,13 +393,6 @@ module Proxy::RemoteExecution::Ssh::Runners
429
393
  @expecting_disconnect = true
430
394
  end
431
395
  end
432
-
433
- def available_authentication_methods
434
- methods = %w[publickey] # Always use pubkey auth as fallback
435
- methods << 'gssapi-with-mic' if settings[:kerberos_auth]
436
- methods.unshift('password') if @ssh_password
437
- methods
438
- end
439
396
  end
440
397
  # rubocop:enable Metrics/ClassLength
441
398
  end
@@ -1,7 +1,7 @@
1
1
  module Proxy
2
2
  module RemoteExecution
3
3
  module Ssh
4
- VERSION = '0.7.3'
4
+ VERSION = '0.9.0'
5
5
  end
6
6
  end
7
7
  end
@@ -32,3 +32,7 @@
32
32
  # unset, SSL gets used if smart-proxy's foreman_ssl_cert, foreman_ssl_key and
33
33
  # foreman_ssl_ca settings are set available.
34
34
  # :mqtt_tls:
35
+
36
+ # The notification is sent over mqtt every $mqtt_resend_interval seconds, until
37
+ # the job is picked up by the host or cancelled
38
+ # :mqtt_resend_interval: 900
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_remote_execution_ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-27 00:00:00.000000000 Z
11
+ date: 2022-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -170,10 +170,12 @@ files:
170
170
  - lib/smart_proxy_remote_execution_ssh/async_scripts/control.sh
171
171
  - lib/smart_proxy_remote_execution_ssh/async_scripts/retrieve.sh
172
172
  - lib/smart_proxy_remote_execution_ssh/cockpit.rb
173
+ - lib/smart_proxy_remote_execution_ssh/command_logging.rb
173
174
  - lib/smart_proxy_remote_execution_ssh/dispatcher.rb
174
175
  - lib/smart_proxy_remote_execution_ssh/http_config.ru
175
176
  - lib/smart_proxy_remote_execution_ssh/job_storage.rb
176
177
  - lib/smart_proxy_remote_execution_ssh/log_filter.rb
178
+ - lib/smart_proxy_remote_execution_ssh/multiplexed_ssh_connection.rb
177
179
  - lib/smart_proxy_remote_execution_ssh/net_ssh_compat.rb
178
180
  - lib/smart_proxy_remote_execution_ssh/plugin.rb
179
181
  - lib/smart_proxy_remote_execution_ssh/runners.rb