Files
ubicloud/bin/ubi
Jeremy Evans 912b2fbc30 Refactor how exec command args are passed from server to ubi
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.
2025-02-05 11:01:58 -08:00

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