Run 'some' code as a different user? NET::SSH?

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

···

--
Posted via http://www.ruby-forum.com/.

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

Perhaps you could use PTY.spawn and run 'su - username'

···

On Tue, May 01, 2007 at 04:48:37AM +0900, John Clisham wrote:

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?

Brian Candler 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

···

On Tue, May 01, 2007 at 04:48:37AM +0900, John Clisham wrote:

--
Posted via http://www.ruby-forum.com/\.

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.

···

On 4/30/07, John Clisham <jfclisham@gmail.com> wrote:

Brian Candler wrote:
> On Tue, May 01, 2007 at 04:48:37AM +0900, John Clisham 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

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 :slight_smile:

(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.

···

On Tue, May 01, 2007 at 05:34:40AM +0900, John Clisham wrote:

Jano Svitok wrote:

···

On 4/30/07, John Clisham <jfclisham@gmail.com> 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

--
Posted via http://www.ruby-forum.com/\.

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.

···

On Tue, May 01, 2007 at 04:46:48PM +0900, Brian Candler 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.

Brian,

WOW!!! Thank you very much for taking so much of your time to help! :slight_smile:
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

···

--
Posted via http://www.ruby-forum.com/.