smart_proxy_remote_execution_ssh 0.7.3 → 0.9.0

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