Forum: Ruby run 'some' code as a different user? NET::SSH?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
John C. (Guest)
on 2007-04-30 23:48
Hello,

I'm working on a ROR app that allows users to track batch computing jobs
submitted to our high performance computing clusters. Anyway, a piece of
functionality to that application is an online file browser to the
server.

Net, I have a need to authenticate users to the Server's OS (I was
thinking using Net:SSH).  Then, once authenticated, I'd like to run some
ruby commands as that user vs. the 'non-priviledged user the webserver
is running under.

Is this possible?  Any ideas?

Thanks
KF
Brian C. (Guest)
on 2007-05-01 00:24
(Received via mailing list)
On Tue, May 01, 2007 at 04:48:37AM +0900, John C. wrote:
> is running under.
>
> Is this possible?  Any ideas?

The user will authenticate by providing their OS username and password?

Perhaps you could use PTY.spawn and run 'su - username'
John C. (Guest)
on 2007-05-01 00:34
Brian C. wrote:
> On Tue, May 01, 2007 at 04:48:37AM +0900, John C. wrote:
>> is running under.
>>
>> Is this possible?  Any ideas?
>
> The user will authenticate by providing their OS username and password?
>
> Perhaps you could use PTY.spawn and run 'su - username'


Yes, I will authenticate useing their OS username and password. I
haven't used PTY.spawn before.  Does it just spawn off a new psuedo
terminal?  After doing a 'su -username' and authenicating; I want to
execute a block of ruby code as that user.  Ideally that block would
return a ruby object that I could then manipulate in the broader app.
(ie since this is a Rails app, I would execute controller code as the
authenticated user (ie to get job files); and use one set of views for
all users.

Am I making sense or over thinking this?  Basically, I'm trying to avoid
calling everything in shell's and reading in stdout.  I'm trying to stay
completely in Ruby here.

THanks!
KF
Jano S. (Guest)
on 2007-05-01 01:03
(Received via mailing list)
On 4/30/07, John C. <removed_email_address@domain.invalid> wrote:
>
> calling everything in shell's and reading in stdout.  I'm trying to stay
> completely in Ruby here.
>
> THanks!
> KF

Ruby is one process to the OS - so most probably it cannot have
credentials of more users. What you can do is have 'session' have
another process running with respective user's credentials and
communicate with it with drb or similar. You would need to keep a pool
of these and kill/stop them when the session is done or timeouted.

You probably won't be able to avoid more processes though, but you may
avoid communicating through stdout.

Or, maybe you can avoid all of this if the user under which the the
webserver is running has rights to users' files (i.e. they all belong
to the same group and have the rights set appropriately).

Finally, you will want to double-check your code for security issues...

HTH.
J.
John C. (Guest)
on 2007-05-01 01:16
Jano S. wrote:
> On 4/30/07, John C. <removed_email_address@domain.invalid> wrote:
>>
>> calling everything in shell's and reading in stdout.  I'm trying to stay
>> completely in Ruby here.
>>
>> THanks!
>> KF
>
> Ruby is one process to the OS - so most probably it cannot have
> credentials of more users. What you can do is have 'session' have
> another process running with respective user's credentials and
> communicate with it with drb or similar. You would need to keep a pool
> of these and kill/stop them when the session is done or timeouted.
>
> You probably won't be able to avoid more processes though, but you may
> avoid communicating through stdout.
>
> Or, maybe you can avoid all of this if the user under which the the
> webserver is running has rights to users' files (i.e. they all belong
> to the same group and have the rights set appropriately).
>
> Finally, you will want to double-check your code for security issues...
>
> HTH.
> J.


J.

Very helpful thanks! Ironically, I was just starting to look into this
method.  Looks like my 'little' app just got much more complex!

Thanks!
John
Brian C. (Guest)
on 2007-05-01 11:48
(Received via mailing list)
On Tue, May 01, 2007 at 05:34:40AM +0900, John C. wrote:
> Yes, I will authenticate useing their OS username and password. I
> haven't used PTY.spawn before.  Does it just spawn off a new psuedo
> terminal?  After doing a 'su -username' and authenicating; I want to
> execute a block of ruby code as that user.

I was thinking of something like this (untested):

username = "foo"
password = "bar"

require 'pty'
require 'expect'
inp, out = PTY.spawn("su - #{username}")
inp.expect /assword: /
out.puts password
inp.expect /\$ /
out.puts "ruby /path/to/foo.rb"

> Ideally that block would
> return a ruby object that I could then manipulate in the broader app.

That's more difficult, because your web server is running as one uid and
the
other part is running as a completely different uid, and therefore by
definition must be in a separate process.

You can run a DRb server in the child process, and have a DRb client
(proxy)
object in the web service. This will work fine, but DRb doesn't run over
stdin/stdout, so you would have to bind each child process to a
different
port number.

This isn't unsurmountable though. I think you can let DRb in the child
pick
a port number dynamically, and then send that number back over stdout
(which
you then pick up by reading the PTY)

There's a DRb tutorial at
http://wiki.rubygarden.org/Ruby/page/show/DrbTutorial
which you may find useful. (Personal interest disclaimer: I wrote it :-)

> (ie since this is a Rails app, I would execute controller code as the
> authenticated user (ie to get job files); and use one set of views for
> all users.

It seems you have hit against the brick wall which many PHP hosting
providers come across: namely, they want to run Apache with mod_php for
efficiency, but they don't want one user's PHP scripts to be able to
access
or modify another user's files. Since Apache runs as a single user, and
mod_php runs as that user, this turns out to be tricky.

One solution is to run each user's applications as fastcgi scripts,
where
the users' code runs as a pool of separate processes running under their
own
uid. A similar solution is to give each user their own completely
separate
webserver instance (e.g. httpd), either bound to a separate IP, or bound
to
a separate port with a proxy in front which routes the incoming HTTP
requests to the right webserver instance.

In your case, this would effectively mean running multiple copies of
your
entire Rails app, one under each user ID. If the number of users you
have is
not large, this is probably a reasonable approach.

You could do this statically, e.g. with webrick or mongrel (run a
separate
application instance for each user, bound to a separate port, running as
their userid). Or, although I've not tried it myself, it should also be
possible to run Rails under fastcgi with suexec or cgiwrap. The
advantage of
this approach is that when a user is no longer active, the webserver can
completely kill off their Rails application instance.

In both cases, the app would then take on responsibility for
authenticating
the user. Once they had authenticated, the controller would have rights
to
do whatever that user could do.

Regards,

Brian.
Brian C. (Guest)
on 2007-05-01 12:08
(Received via mailing list)
On Tue, May 01, 2007 at 04:46:48PM +0900, Brian C. wrote:
> One solution is to run each user's applications as fastcgi scripts, where
> the users' code runs as a pool of separate processes running under their own
> uid. A similar solution is to give each user their own completely separate
> webserver instance (e.g. httpd), either bound to a separate IP, or bound to
> a separate port with a proxy in front which routes the incoming HTTP
> requests to the right webserver instance.
>
> In your case, this would effectively mean running multiple copies of your
> entire Rails app, one under each user ID. If the number of users you have is
> not large, this is probably a reasonable approach.

Another approach to consider, if the number of types of different
requests
your users need to implement is small, is to have a small C setuid
wrapper
program which in turn performs an action after validating the
parameters.

You'd then invoke it as, say,

    system("/path/to/rubywrap",username,command,arg)

rubywrap.c would be a small C program which checks command and arg are
"safe", changes userid to username, and then does whatever command
implies.
It would then be installed setuid root, and possibly also made only
executable by the webserver user or group.

However, I wouldn't recommend going this way unless you fully understand
the
security implications of this. Find someone else's implementation of
something similar (e.g. Apache suexec) and understand all the subtleties
of
its implementation, before trying it yourself.

Regards,

Brian.
John C. (Guest)
on 2007-05-01 16:00
Brian,

WOW!!! Thank you very much for taking so much of your time to help! :)
Pretty amazing.

Anyway, I think I may try your first approach of initiating a separate
Rails process per user.  We're only realistically talking about 10
concurrent users at any given time.  The webserver is relatively beefy
so it should be able to handle it with ease (4 X86_64 cpu's; 8GB RAM).

I'm also going to dive a bit further into Option 2.  The setuid wrapper
seems like a good plan.  Unfortunately, I'm no security expert; but our
HPC Systems Admins are very strong.  Net, I can work the solution
through them..

Thanks!
John
This topic is locked and can not be replied to.