Restricted file upload to remote the Ruby way I am using a Rails web app on the machine webapp, from which I call an action, that pushes a file to a remote server backend. Due to security reasons, we are using SSH keys and put the public part into user@backend:~/.ssh/authorized_keys. The keys are restricted, so the Rails app does not have full control over user, like this:

from="webapp",command="touch datafile; cat &gt; datafile" ssh-rsa ...


The Ruby method looks like this

def self.push_file
  errors = `cat datafile | ssh -i id_rsa user@backend 'true' 2&gt;&amp;1`
  raise errors unless errors.empty?
end


What the Ruby code does is, it cats the file and then connects to backend via SSH, where it executes the "command" true, which then executes the command specified in the user@backend:~/.ssh/authorized_keys file (create datafile, write content). Lastly, the stdout is overwritten by stderr, so I can notice errors, which may occurr.

The solution works (it might be noteworthy, that datafile is always &lt;100KiB), but seems dirty in three ways:


I am not using SCP for doing what is essentially a copy.


Possible fix: Find a way to restrict SCP to allow exactly this operation, nothing else

I am using neither Net::SSH nor Net::SCP, but instead raw commands, the first is due to the need of pipes in order to execute the first command on webapp and the second one on backend; and the latter due to the non-restriction of SCP.
stdout is overwritten by stderr in order to capture errors (least problematic, I know about popen)


Any suggestions on how to make the solution cleaner?
I may be mistaken but I really do not see why using Net::SCP would be any different (more dangerous) than copying by piping through ssh. It is not like by using Net::SCP you would be giving somebody more rights, it is all contained within your code and under your control.

If somebody tries to misuse Net::SCP he can also misuse the cat solution copying anything else the user user has access to.

But - AFAIK 2&gt;&amp;1 doesn’t really overwrite stdout by stderr, it appends stderr to stdout, in which case you would be raising an error also on output and not only on error.

Try this instead:

def self.push_file
    output = `cat datafile | (ssh -i id_rsa user@backend 'true' | sed 's/^/O: /' &gt;&amp;9 ) 9&gt;&amp;2 2&gt;&amp;1 | sed 's/^/E: /'`
    errors = output.select{|e|e.match(/^E:/)}.map{|e|e.sub(/^E: /, "")}
    raise errors unless errors.empty?
end


Found in In the shell, what does “ 2>&amp;1 ” mean?

If not using SCP you can still improve on this pipe solution by using rsync :

rsync -avz -e "ssh -i id_rsa" /home/user/datafile backend:/home/user/