3
\$\begingroup\$

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 > datafile" ssh-rsa ...

The Ruby method looks like this

def self.push_file
  errors = `cat datafile | ssh -i id_rsa user@backend 'true' 2>&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 <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?

\$\endgroup\$
  • \$\begingroup\$ Have you looked at Allow SCP but not actual login using SSH? \$\endgroup\$ – 200_success Jul 15 '16 at 8:13
  • \$\begingroup\$ Good hint, but I could also just use the normal SCP then. The thing with SCP is, I cannot restrict access to certain files. The pubkey should only have access to user@backend:~/datafile and nothing else. Using SCP, however, I could up/download whatever I like to/from anywhere user has access to on backend. \$\endgroup\$ – Brauser Jul 15 '16 at 8:23
  • \$\begingroup\$ Restricting an SSH/SCP/SFTP user to a directory \$\endgroup\$ – 200_success Jul 15 '16 at 8:25
  • \$\begingroup\$ Thanks, sadly I am not administrating the machine, and currently we have for each Rails app a subdirectory in user@backend:~ to call network scripts etc from, so I may not restrict user as a whole, since developers from other apps might need to use SFTP for their apps as well. Also I can't use Match User user@webapp, since there are multiple Rails apps on webapp. Either I'm missing something, or "cleaning the solution" is not doable for my case. \$\endgroup\$ – Brauser Jul 15 '16 at 8:44
1
\$\begingroup\$

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>&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: /' >&9 ) 9>&2 2>&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>&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/
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.