class Proxy::RemoteExecution::Cockpit::Session

Public Class Methods

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

Public Instance Methods

hijack!() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 122
def hijack!
  @socket = nil
  if @env['ext.hijack!']
    @socket = @env['ext.hijack!'].call
  elsif @env['rack.hijack?']
    begin
      @env['rack.hijack'].call
    rescue NotImplementedError
    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 118
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 236
def buf_socket
  @buffered_socket ||= BufferedSocket.build(@socket)
end
command() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 240
def command
  params["command"]
end
host() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 248
def host
  params["hostname"]
end
key_file() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 232
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 228
def params
  @params ||= MultiJson.load(@env["rack.input"].read)
end
send_error(code, msg) click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 221
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)
end
send_start() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 211
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")
  end
end
ssh_on_socket() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 139
def ssh_on_socket
  with_error_handling { start_ssh_loop }
end
ssh_options() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 252
def ssh_options
  auth_methods = %w[publickey]
  auth_methods.unshift('password') if params["ssh_password"]

  ret = {}
  ret[:port] = params["ssh_port"] if params["ssh_port"]
  ret[:keys] = [key_file] if key_file
  ret[:password] = params["ssh_password"] if params["ssh_password"]
  ret[:passphrase] = params["ssh_key_passphrase"] if params["ssh_key_passphrase"]
  ret[:keys_only] = true
  ret[:auth_methods] = auth_methods
  ret[:verify_host_key] = true
  ret[:number_of_password_prompts] = 1
  ret
end
ssh_user() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 244
def ssh_user
  params["ssh_user"]
end
start_ssh_loop() click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 164
def start_ssh_loop
  err_buf = ""

  Net::SSH.start(host, ssh_user, ssh_options) do |ssh|
    channel = ssh.open_channel do |ch|
      ch.exec(command) do |ch, success|
        raise "could not execute command" unless success

        ssh.listen_to(buf_socket)

        ch.on_process do
          if buf_socket.available.positive?
            ch.send_data(buf_socket.read_available)
          end
          if buf_socket.closed?
            ch.close
          end
        end

        ch.on_data do |ch2, data|
          send_start
          buf_socket.enqueue(data)
        end

        ch.on_request('exit-status') do |ch, data|
          code = data.read_long
          send_start if code.zero?
          err_buf += "Process exited with code #{code}.\r\n"
          ch.close
        end

        ch.on_request('exit-signal') do |ch, data|
          err_buf += "Process was terminated with signal #{data.read_string}.\r\n"
          ch.close
        end

        ch.on_extended_data do |ch2, type, data|
          err_buf += data
        end
      end
    end

    channel.wait
    send_error(400, err_buf) unless @started
  end
end
with_error_handling() { || ... } click to toggle source
# File lib/smart_proxy_remote_execution_ssh/cockpit.rb, line 143
def with_error_handling
  yield
rescue Net::SSH::AuthenticationFailed => e
  send_error(401, e.message)
rescue Errno::EHOSTUNREACH
  send_error(400, "No route to #{host}")
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