class Proxy::RemoteExecution::Cockpit::Session

Public Class Methods

new(env) click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 115
def initialize(env)
  @env = env
  @open_ios = []
end

Public Instance Methods

hijack!() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 124
def hijack!
  @socket = nil
  if @env['ext.hijack!']
    @socket = @env['ext.hijack!'].call
  elsif @env['rack.hijack?']
    begin
      @env['rack.hijack'].call
    rescue NotImplementedError
      # This is fine
    end
    @socket = @env['rack.hijack_io']
  end
  raise 'Internal error: request hijacking not available' unless @socket
  ssh_on_socket
end
valid?() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 120
def valid?
  @env["HTTP_CONNECTION"] == "upgrade" && @env["HTTP_UPGRADE"].to_s.split(',').any? { |part| part.strip == "raw" }
end

Private Instance Methods

buf_socket() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 256
def buf_socket
  @buf_socket ||= BufferedSocket.build(@socket)
end
command() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 260
def command
  params["command"]
end
connection() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 268
def connection
  @connection ||= Proxy::RemoteExecution::Ssh::Runners::MultiplexedSSHConnection.new(
    runner_params,
    logger: logger
  )
end
flush_pending_writes(writers) click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 223
def flush_pending_writes(writers)
  writers.each do |writer|
    writer.respond_to?(:send_pending) ? writer.send_pending : writer.flush
  end
end
host() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 264
def host
  params["hostname"]
end
inner_system_ssh_loop(out_buf, err_buf, in_buf, pid) click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 183
def inner_system_ssh_loop(out_buf, err_buf, in_buf, pid)
  err_buf_raw = ''
  loop do
    readers = [buf_socket, out_buf, err_buf].reject { |io| io.closed? }
    writers = [buf_socket, in_buf].select { |io| io.pending_writes? }
    # Prime the sockets for reading
    ready_readers, ready_writers = IO.select(readers, writers)
    (ready_readers || []).each { |reader| reader.close if reader.fill.zero? }

    proxy_data(out_buf, in_buf)
    if buf_socket.closed?
      connection.disconnect!
    end

    if out_buf.closed?
      code = Process.wait2(pid).last.exitstatus
      send_start if code.zero? # TODO: Why?
      err_buf_raw += "Process exited with code #{code}.\r\n"
      break
    end

    if err_buf.available.positive?
      err_buf_raw += err_buf.read_available
    end

    flush_pending_writes(ready_writers || [])
  end
rescue # rubocop:disable Style/RescueStandardError
  send_error(400, err_buf_raw) unless @started
ensure
  [out_buf, err_buf, in_buf].each(&:close)
end
key_file() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 252
def key_file
  @key_file ||= Proxy::RemoteExecution::Ssh.private_key_file
end
params() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 248
def params
  @params ||= MultiJson.load(@env["rack.input"].read)
end
proxy_data(out_buf, in_buf) click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 216
def proxy_data(out_buf, in_buf)
  { out_buf => buf_socket, buf_socket => in_buf }.each do |src, dst|
    dst.enqueue(src.read_available) if src.available.positive?
    dst.close if src.closed?
  end
end
runner_params() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 275
def runner_params
  ret = { secrets: {} }
  ret[:secrets][:ssh_password] = params["ssh_password"] if params["ssh_password"]
  ret[:secrets][:key_passphrase] = params["ssh_key_passphrase"] if params["ssh_key_passphrase"]
  ret[:ssh_port] = params["ssh_port"] if params["ssh_port"]
  ret[:ssh_user] = params["ssh_user"]
  # For compatibility only
  ret[:script] = nil
  ret[:hostname] = host
  ret[:id] = SecureRandom.uuid
  ret
end
send_error(code, msg) click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 240
def send_error(code, msg)
  buf_socket.enqueue("Status: #{code}\r\n")
  buf_socket.enqueue("Connection: close\r\n")
  buf_socket.enqueue("\r\n")
  buf_socket.enqueue(msg)
  buf_socket.send_pending
end
send_start() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 229
def send_start
  unless @started
    @started = true
    buf_socket.enqueue("Status: 101\r\n")
    buf_socket.enqueue("Connection: upgrade\r\n")
    buf_socket.enqueue("Upgrade: raw\r\n")
    buf_socket.enqueue("\r\n")
    buf_socket.send_pending
  end
end
ssh_on_socket() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 142
def ssh_on_socket
  with_error_handling { system_ssh_loop }
end
system_ssh_loop() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 163
def system_ssh_loop
  in_read, in_write   = IO.pipe
  out_read, out_write = IO.pipe
  err_read, err_write = IO.pipe

  connection.establish!
  pid = spawn(*connection.command(command), :in => in_read, :out => out_write, :err => err_write)
  [in_read, out_write, err_write].each(&:close)

  send_start
  # Not SSL buffer, but the interface kinda matches
  out_buf = MiniSSLBufferedSocket.new(out_read)
  err_buf = MiniSSLBufferedSocket.new(err_read)
  in_buf  = MiniSSLBufferedSocket.new(in_write)

  inner_system_ssh_loop out_buf, err_buf, in_buf, pid
ensure
  connection.disconnect!
end
with_error_handling() { || ... } click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 146
def with_error_handling
  yield
rescue SystemCallError => e
  send_error(400, e.message)
rescue SocketError => e
  send_error(400, e.message)
rescue Exception => e
  logger.error e.message
  logger.debug e.backtrace.join("\n")
  send_error(500, "Internal error") unless @started
ensure
  unless buf_socket.closed?
    buf_socket.wait_for_pending_sends
    buf_socket.close
  end
end