smart_proxy_remote_execution_ssh 0.7.0 → 0.8.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 +4 -4
- data/lib/smart_proxy_remote_execution_ssh/cockpit.rb +13 -9
- data/lib/smart_proxy_remote_execution_ssh/command_logging.rb +23 -0
- data/lib/smart_proxy_remote_execution_ssh/multiplexed_ssh_connection.rb +196 -0
- data/lib/smart_proxy_remote_execution_ssh/net_ssh_compat.rb +6 -2
- data/lib/smart_proxy_remote_execution_ssh/plugin.rb +1 -1
- data/lib/smart_proxy_remote_execution_ssh/runners/polling_script_runner.rb +3 -3
- data/lib/smart_proxy_remote_execution_ssh/runners/script_runner.rb +19 -61
- data/lib/smart_proxy_remote_execution_ssh/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a599d3732d4f6b064a850ae8baced28354980096785a340828533f953f844c4
|
|
4
|
+
data.tar.gz: 160b80ab983f0eb7b987987191b48c19db42d3b76079b0ba6639ec831dccc6f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 349961aa474809225d9781bda09687eb23f00db51cca9f758a57e075fa3d19fc07b729569b064887f91bb7f07190152d6ec9cb01e8ccf48df0841e9c3f85d10e
|
|
7
|
+
data.tar.gz: 8ad40859c92f7d1cd089adf0f37bb0aaa8add745cd461c2e1ed5b484d3ae6b856ac0ee53e61c62e89d564a4ba676b22bf5f01618da970986cd60e42591ff6346
|
|
@@ -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
|
-
|
|
168
|
-
|
|
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,19 +176,22 @@ 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)
|
|
182
184
|
err_buf_raw = ''
|
|
183
|
-
readers = [buf_socket, out_buf, err_buf]
|
|
184
185
|
loop do
|
|
186
|
+
readers = [buf_socket, out_buf, err_buf].reject { |io| io.closed? }
|
|
187
|
+
writers = [buf_socket, in_buf].select { |io| io.pending_writes? }
|
|
185
188
|
# Prime the sockets for reading
|
|
186
|
-
ready_readers, ready_writers = IO.select(readers,
|
|
189
|
+
ready_readers, ready_writers = IO.select(readers, writers)
|
|
187
190
|
(ready_readers || []).each { |reader| reader.close if reader.fill.zero? }
|
|
188
191
|
|
|
189
192
|
proxy_data(out_buf, in_buf)
|
|
190
193
|
if buf_socket.closed?
|
|
191
|
-
|
|
194
|
+
connection.disconnect!
|
|
192
195
|
end
|
|
193
196
|
|
|
194
197
|
if out_buf.closed?
|
|
@@ -262,10 +265,10 @@ module Proxy::RemoteExecution
|
|
|
262
265
|
params["hostname"]
|
|
263
266
|
end
|
|
264
267
|
|
|
265
|
-
def
|
|
266
|
-
@
|
|
268
|
+
def connection
|
|
269
|
+
@connection ||= Proxy::RemoteExecution::Ssh::Runners::MultiplexedSSHConnection.new(
|
|
267
270
|
runner_params,
|
|
268
|
-
|
|
271
|
+
logger: logger
|
|
269
272
|
)
|
|
270
273
|
end
|
|
271
274
|
|
|
@@ -278,6 +281,7 @@ module Proxy::RemoteExecution
|
|
|
278
281
|
# For compatibility only
|
|
279
282
|
ret[:script] = nil
|
|
280
283
|
ret[:hostname] = host
|
|
284
|
+
ret[:id] = SecureRandom.uuid
|
|
281
285
|
ret
|
|
282
286
|
end
|
|
283
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
|
+
''
|
|
18
|
+
end
|
|
19
|
+
pm.on_stdout(&callback)
|
|
20
|
+
pm.on_stderr(&callback)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -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)}, '/usr/bin/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
|
+
['/usr/bin/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, '/usr/bin/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 = ['/usr/bin/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
|
|
@@ -173,10 +173,14 @@ module Proxy::RemoteExecution
|
|
|
173
173
|
output.append(data)
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
+
def pending_writes?
|
|
177
|
+
output.length.positive?
|
|
178
|
+
end
|
|
179
|
+
|
|
176
180
|
# Sends as much of the pending output as possible. Returns +true+ if any
|
|
177
181
|
# data was sent, and +false+ otherwise.
|
|
178
182
|
def send_pending
|
|
179
|
-
if
|
|
183
|
+
if pending_writes?
|
|
180
184
|
sent = send(output.to_s, 0)
|
|
181
185
|
output.consume!(sent)
|
|
182
186
|
return sent.positive?
|
|
@@ -189,7 +193,7 @@ module Proxy::RemoteExecution
|
|
|
189
193
|
# buffer is empty.
|
|
190
194
|
def wait_for_pending_sends
|
|
191
195
|
send_pending
|
|
192
|
-
while
|
|
196
|
+
while pending_writes?
|
|
193
197
|
result = IO.select(nil, [self]) || next
|
|
194
198
|
next unless result[1].any?
|
|
195
199
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Proxy::RemoteExecution::Ssh
|
|
2
2
|
class Plugin < Proxy::Plugin
|
|
3
3
|
SSH_LOG_LEVELS = %w[debug info error fatal].freeze
|
|
4
|
-
MODES = %i[ssh async
|
|
4
|
+
MODES = %i[ssh ssh-async pull pull-mqtt].freeze
|
|
5
5
|
# Unix domain socket path length is limited to 104 (on some platforms) characters
|
|
6
6
|
# Socket path is composed of custom path (max 49 characters) + job id (37 characters)
|
|
7
7
|
# + offset(17 characters) + null terminator
|
|
@@ -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
|
|
@@ -139,7 +139,7 @@ module Proxy::RemoteExecution::Ssh::Runners
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
def destroy_session
|
|
142
|
-
if @
|
|
142
|
+
if @connection.connected?
|
|
143
143
|
@logger.debug("Closing session with #{@ssh_user}@#{@host}")
|
|
144
144
|
close_session
|
|
145
145
|
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
|
-
|
|
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
|
-
|
|
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,34 +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
|
-
end
|
|
283
|
-
|
|
284
256
|
def settings
|
|
285
257
|
Proxy::RemoteExecution::Ssh::Plugin.settings
|
|
286
258
|
end
|
|
287
259
|
|
|
288
|
-
def get_args(command, with_pty = false, quiet: false)
|
|
289
|
-
args = []
|
|
290
|
-
args = [{'SSHPASS' => @key_passphrase}, '/usr/bin/sshpass', '-P', 'passphrase', '-e'] if @key_passphrase
|
|
291
|
-
args = [{'SSHPASS' => @ssh_password}, '/usr/bin/sshpass', '-e'] if @ssh_password
|
|
292
|
-
args += ['/usr/bin/ssh', @host, ssh_options(with_pty, quiet: quiet), command].flatten
|
|
293
|
-
end
|
|
294
|
-
|
|
295
260
|
# Initiates run of the remote command and yields the data when
|
|
296
261
|
# available. The yielding doesn't happen automatically, but as
|
|
297
262
|
# part of calling the `refresh` method.
|
|
@@ -299,7 +264,9 @@ module Proxy::RemoteExecution::Ssh::Runners
|
|
|
299
264
|
raise 'Async command already in progress' if @process_manager&.started?
|
|
300
265
|
|
|
301
266
|
@user_method.reset
|
|
302
|
-
|
|
267
|
+
cmd = @connection.command([tty_flag(true), command].flatten.compact)
|
|
268
|
+
log_command(cmd)
|
|
269
|
+
initialize_command(*cmd)
|
|
303
270
|
|
|
304
271
|
true
|
|
305
272
|
end
|
|
@@ -308,17 +275,15 @@ module Proxy::RemoteExecution::Ssh::Runners
|
|
|
308
275
|
@process_manager&.started? && @user_method.sent_all_data?
|
|
309
276
|
end
|
|
310
277
|
|
|
278
|
+
def tty_flag(tty)
|
|
279
|
+
'-tt' if tty
|
|
280
|
+
end
|
|
281
|
+
|
|
311
282
|
def run_sync(command, stdin: nil, close_stdin: true, tty: false, user_method: nil)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
user_method.on_data(data, pm.stdin) if user_method
|
|
317
|
-
end
|
|
318
|
-
''
|
|
319
|
-
end
|
|
320
|
-
pm.on_stdout(&callback)
|
|
321
|
-
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)
|
|
322
287
|
pm.start!
|
|
323
288
|
unless pm.status
|
|
324
289
|
pm.stdin.io.puts(stdin) if stdin
|
|
@@ -428,13 +393,6 @@ module Proxy::RemoteExecution::Ssh::Runners
|
|
|
428
393
|
@expecting_disconnect = true
|
|
429
394
|
end
|
|
430
395
|
end
|
|
431
|
-
|
|
432
|
-
def available_authentication_methods
|
|
433
|
-
methods = %w[publickey] # Always use pubkey auth as fallback
|
|
434
|
-
methods << 'gssapi-with-mic' if settings[:kerberos_auth]
|
|
435
|
-
methods.unshift('password') if @ssh_password
|
|
436
|
-
methods
|
|
437
|
-
end
|
|
438
396
|
end
|
|
439
397
|
# rubocop:enable Metrics/ClassLength
|
|
440
398
|
end
|
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.
|
|
4
|
+
version: 0.8.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:
|
|
11
|
+
date: 1980-01-01 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
|
|
@@ -203,7 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
203
205
|
- !ruby/object:Gem::Version
|
|
204
206
|
version: '0'
|
|
205
207
|
requirements: []
|
|
206
|
-
rubygems_version: 3.
|
|
208
|
+
rubygems_version: 3.2.26
|
|
207
209
|
signing_key:
|
|
208
210
|
specification_version: 4
|
|
209
211
|
summary: Ssh remote execution provider for Foreman Smart-Proxy
|