Previously, there was a set of headers used: * ubi-command-execute: For the command to execute * ubi-command-arg: For the argument to the command * ubi-command-argv-tail: For the argv index to include the rest of argv in command * ubi-command-argv-initial: For the argv index to include before the argument to the command This was too limiting, as it doesn't allow specifying both options and arguments to exec-ed commands. This changes things so that ubi-command-execute still specifies the command, but the arguments are passed in the response body, separated by "\0". The client checks each argument for validity: * At least one argument must be "--" * At most one argument not already present in argv is allowed * The argument not already present in argv must be after "--" So the worst a malicious server can do is rearrange argument order and insert a single new argument, which will be parsed as an argument and not as an option. That's about the same security as before, with a lot less complexity and a lot more flexibility. Use the new flexibility to support passing options to the vm ssh, vm sftp, and vm scp commands: vm ssh location vm-name -A -- vm sftp location vm-name -A vm scp location vm-name local-path :remote-path -A For vm ssh, the -- is required to separate ssh options from ssh arguments. In addition to making the code significantly less complex, this makes the specs much more understandable. To ease debugging of bin/ubi, add support for an UBI_DEBUG environment variable, which will print the arguments passed to Process.exec, so you can more easily confirm correct behavior.
91 lines
2.0 KiB
Ruby
Executable File
91 lines
2.0 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
# frozen_string_literal: true
|
|
|
|
begin
|
|
require_relative "../.env"
|
|
rescue LoadError
|
|
end
|
|
|
|
require "net/http"
|
|
require "json"
|
|
|
|
unless (token = ENV["UBI_TOKEN"])
|
|
warn "Personal access token must be provided in UBI_TOKEN env variable for use"
|
|
exit 1
|
|
end
|
|
|
|
url = ENV["UBI_URL"] || "http://api.localhost:9292/cli"
|
|
allowed_progs = %w[ssh scp sftp psql]
|
|
|
|
get_prog = lambda do |prog|
|
|
return unless allowed_progs.include?(prog)
|
|
ENV["UBI_#{prog.upcase}"] || prog
|
|
end
|
|
|
|
uri = URI(url)
|
|
data = {"argv" => ARGV}.to_json
|
|
headers = {
|
|
"authorization" => "Bearer: #{token}",
|
|
"content-type" => "application/json",
|
|
"accept" => "text/plain",
|
|
"connection" => "close"
|
|
}
|
|
|
|
response = Net::HTTP.post(uri, data, headers)
|
|
|
|
case response.code.to_i
|
|
when 200...300
|
|
if (prog = response["ubi-command-execute"])
|
|
unless (prog = get_prog[prog])
|
|
warn "Invalid server response, unsupported program requested"
|
|
exit 1
|
|
end
|
|
|
|
argv_set = ARGV.to_set
|
|
|
|
args = response.body.split("\0")
|
|
invalid_message = nil
|
|
sep_seen = false
|
|
custom_arg_seen = false
|
|
|
|
args.each do |arg|
|
|
if arg == "--"
|
|
sep_seen = true
|
|
elsif !argv_set.include?(arg)
|
|
if custom_arg_seen
|
|
invalid_message = "Invalid server response, multiple arguments not in submitted argv"
|
|
break
|
|
elsif sep_seen
|
|
custom_arg_seen = true
|
|
else
|
|
invalid_message = "Invalid server response, argument before '--' not in submitted argv"
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
unless sep_seen
|
|
invalid_message = "Invalid server response, no '--' in returned argv"
|
|
end
|
|
|
|
if invalid_message
|
|
if ENV["UBI_DEBUG"] == "1"
|
|
p [:failure, prog, *args]
|
|
end
|
|
warn invalid_message
|
|
exit 1
|
|
elsif (prog = get_prog[prog])
|
|
if ENV["UBI_DEBUG"] == "1"
|
|
p [:exec, prog, *args]
|
|
end
|
|
Process.exec(prog, *args)
|
|
end
|
|
else
|
|
$stdout.puts response.body
|
|
exit 0
|
|
end
|
|
else
|
|
warn response.body
|
|
exit 1
|
|
end
|