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.
90 lines
2 KiB
Ruby
Executable file
90 lines
2 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
|