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 cat
s 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
norNet::SCP
, but instead raw commands, the first is due to the need of pipes in order to execute the first command onwebapp
and the second one onbackend
; and the latter due to the non-restriction of SCP. stdout
is overwritten bystderr
in order to capture errors (least problematic, I know aboutpopen
)
Any suggestions on how to make the solution cleaner?
1 Answer 1
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/
user@backend:~/datafile
and nothing else. Using SCP, however, I could up/download whatever I like to/from anywhereuser
has access to onbackend
. \$\endgroup\$user@backend:~
to call network scripts etc from, so I may not restrictuser
as a whole, since developers from other apps might need to use SFTP for their apps as well. Also I can't useMatch User user@webapp
, since there are multiple Rails apps onwebapp
. Either I'm missing something, or "cleaning the solution" is not doable for my case. \$\endgroup\$