su {block of code.}

Discussion in 'Ruby' started by Guido De Rosa, Oct 18, 2010.

  1. Hi!

    Scenario: on a Unix-like system I run a ruby program under a
    non-privileged user. Occasionally I need to gain root privileges.

    For external commands it's just a matter of installing sudo, editing
    /etc/sudoers properly, and going:

    system "sudo command..."

    But what if I want to do the same for a block of code?

    Something like this would be really, really cool:

    include Sudo
    su do
    # ruby code...
    end

    There's a way to get this? A gem? Any idea on how to implement it?

    Thanks,
    Guido

    --
    Posted via http://www.ruby-forum.com/.
    Guido De Rosa, Oct 18, 2010
    #1
    1. Advertising

  2. Guido De Rosa

    Tony Arcieri Guest

    [Note: parts of this message were removed to make it a legal post.]

    I'd suggest spawning a new Ruby instance which runs under sudo and talking
    to it with DRb

    On Mon, Oct 18, 2010 at 10:32 AM, Guido De Rosa <>wrote:

    > Hi!
    >
    > Scenario: on a Unix-like system I run a ruby program under a
    > non-privileged user. Occasionally I need to gain root privileges.
    >
    > For external commands it's just a matter of installing sudo, editing
    > /etc/sudoers properly, and going:
    >
    > system "sudo command..."
    >
    > But what if I want to do the same for a block of code?
    >
    > Something like this would be really, really cool:
    >
    > include Sudo
    > su do
    > # ruby code...
    > end
    >
    > There's a way to get this? A gem? Any idea on how to implement it?
    >
    > Thanks,
    > Guido
    >
    > --
    > Posted via http://www.ruby-forum.com/.
    >
    >



    --
    Tony Arcieri
    Medioh! A Kudelski Brand
    Tony Arcieri, Oct 18, 2010
    #2
    1. Advertising

  3. Tony Arcieri wrote in post #955183:
    > I'd suggest spawning a new Ruby instance which runs under sudo and
    > talking
    > to it with DRb


    Or use IO.popen and talk over stdin/stdout.

    (It would be cool if the DRb protocol could be piped over stdin/stdout -
    I looked into it once but it was actually not easy to modify the
    existing DRb code to do that, and it gets hairy with callbacks anyway)

    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Oct 18, 2010
    #3
  4. On 10/18/2010 06:32 PM, Guido De Rosa wrote:
    > Hi!
    >
    > Scenario: on a Unix-like system I run a ruby program under a
    > non-privileged user. Occasionally I need to gain root privileges.
    >
    > For external commands it's just a matter of installing sudo, editing
    > /etc/sudoers properly, and going:
    >
    > system "sudo command..."
    >
    > But what if I want to do the same for a block of code?
    >
    > Something like this would be really, really cool:
    >
    > include Sudo
    > su do
    > # ruby code...
    > end
    >
    > There's a way to get this? A gem? Any idea on how to implement it?


    You cannot do that in a single process since Unix permissions and user
    identity are managed on a per process basis. And then there's the issue
    of authentication, i.e. you probably need a user to enter his password.

    Kind regards

    robert
    Robert Klemme, Oct 18, 2010
    #4
  5. Brian Candler wrote in post #955241:
    > Tony Arcieri wrote in post #955183:
    >> I'd suggest spawning a new Ruby instance which runs under sudo and
    >> talking
    >> to it with DRb

    >
    > Or use IO.popen and talk over stdin/stdout.
    >
    > (It would be cool if the DRb protocol could be piped over stdin/stdout -
    > I looked into it once but it was actually not easy to modify the
    > existing DRb code to do that, and it gets hairy with callbacks anyway)


    Well, if you worry about TCP not being optimal inside the same machine,
    DRb may use unix sockets...

    --
    Posted via http://www.ruby-forum.com/.
    Guido De Rosa, Oct 18, 2010
    #5
  6. Tony Arcieri wrote in post #955183:
    > I'd suggest spawning a new Ruby instance which runs under sudo and
    > talking
    > to it with DRb


    My big concern comes directly from DRb doc:

    "As blocks (or rather the Proc objects that represent them) are not
    marshallable, the block executes in the local, not the remote, context."

    Following your suggestion, I thought about a solution based on a DRb
    server run as root:


    require 'drb/drb'

    URI="druby://localhost:8787"

    class Executor
    def execute(&blk)
    blk.call
    end
    end

    DRb.start_service(URI, Executor.new)

    DRb.thread.join


    The client code being executed as a non-privileged user:


    require 'drb/drb'

    SERVER_URI="druby://localhost:8787"

    DRb.start_service

    executor = DRbObject.new_with_uri(SERVER_URI)

    executor.execute do
    # a file writable only by root
    File.open('/TEST', 'w'){|f| f.puts 'hello!'}
    end


    BUT I get a permission denied!

    On the other if I don't use Procs, everything works fine:

    server.rb:


    # ...
    class FileWriter
    def write(file, str)
    File.open(file, 'w'){|f| f.puts str}
    end
    end
    #
    DRb.start_service(URI, FileWriter.new)


    client.rb:


    # ...
    writer = DRbObject.new_with_uri(SERVER_URI)

    writer.write '/TEST', 'hello!'

    --
    Posted via http://www.ruby-forum.com/.
    Guido De Rosa, Oct 18, 2010
    #6
  7. Guido De Rosa

    Tony Arcieri Guest

    [Note: parts of this message were removed to make it a legal post.]

    On Mon, Oct 18, 2010 at 4:18 PM, Guido De Rosa <>wrote:

    > Tony Arcieri wrote in post #955183:
    > > I'd suggest spawning a new Ruby instance which runs under sudo and
    > > talking
    > > to it with DRb

    >
    > My big concern comes directly from DRb doc:
    >
    > "As blocks (or rather the Proc objects that represent them) are not
    > marshallable, the block executes in the local, not the remote, context."
    >


    That's not an issue here. They're describing how blocks execute in the local
    context. Here we specifically want a block to run in the scop of the remote
    object.

    You could obtain an object over DRb and instance eval the block in the scope
    of the DRb object.

    Any methods executed would be called on the DRb object.

    --
    Tony Arcieri
    Medioh! A Kudelski Brand
    Tony Arcieri, Oct 18, 2010
    #7
  8. Tony Arcieri:
    > You could obtain an object over DRb and instance eval the block in the
    > scope
    > of the DRb object.
    >
    > Any methods executed would be called on the DRb object.


    Still doesn't work.

    server.rb, run as root:

    DRb.start_service(URI, self) # export the 'main' Object

    client.rb, as a normal user:

    o = DRbObject.new_with_uri(SERVER_URI) #=> main

    o.instance_eval do
    ::File.open('/TEST', 'w'){|f| f.puts 'hello'}
    end

    I keep getting a Permission Denied error (Errno::EACCES)

    And if I change '/TEST' into '/tmp/TEST', It's clearly seen that the
    file has been created by the normal user, not by root.

    G.

    --
    Posted via http://www.ruby-forum.com/.
    Guido De Rosa, Oct 19, 2010
    #8
  9. Guido De Rosa

    Tony Arcieri Guest

    [Note: parts of this message were removed to make it a legal post.]

    On Mon, Oct 18, 2010 at 5:20 PM, Guido De Rosa <>wrote:

    > o.instance_eval do
    > ::File.open('/TEST', 'w'){|f| f.puts 'hello'}
    > end
    >
    > I keep getting a Permission Denied error (Errno::EACCES)
    >
    > And if I change '/TEST' into '/tmp/TEST', It's clearly seen that the
    > file has been created by the normal user, not by root.



    Well yes, this isn't going to work, because you're talking to the File
    singleton object here, not to the main object over DRb.

    I guess my question is what exactly are you trying to accomplish? Do you
    want a small DSL of commands to work with files as root, or are you
    expecting everything to be executed in the context of the setuid root VM?

    If it's the former, try this:

    include FileUtils
    cp "somefile", "anotherfile"

    That should operate as expected. Beyond that, you would need to use
    ParseTree or ripper to extract the Ruby code you want executed on the remote
    VM or something like that, but then you need to ensure that all the
    classes/objects it's using are actually loaded on the new VM.

    For practicality's sake I'd suggest exposing a small DSL for doing what you
    want to do as root. FileUtils provides everything I'd think you need, but
    perhaps you have a use case I'm not envisioning.

    --
    Tony Arcieri
    Medioh! A Kudelski Brand
    Tony Arcieri, Oct 19, 2010
    #9
  10. Tony Arcieri wrote in post #955286:

    > I guess my question is what exactly are you trying to accomplish? Do you
    > want a small DSL of commands to work with files as root, or are you
    > expecting everything to be executed in the context of the setuid root
    > VM?


    My immediate, practical need is to deal with files; but in the longer
    term it would be nice to develop something more general, as I wrote in
    my first post. Or something intermediate, as you will read later.

    > If it's the former, try this:
    >
    > include FileUtils
    > cp "somefile", "anotherfile"


    Actually this works fine:

    # server.rb, run as root
    # ...
    DRb.start_service(URI, File)

    # client.rb, non-root
    # ...
    module Sudo
    File = DRbObject.new_with_uri(SERVER_URI)
    end

    puts Sudo::File.read '/etc/shadow' # only readable by root

    It also works with FileUtils instead of Files and probably other classes
    and modules.

    But what if I want to distribute multiple classes/modules? In general,
    what is the proper way to distribute multiple dRuby front objects?

    The most obvious solution, to me, was an Array of objects as a front
    object.

    I tried this:

    # server.rb
    DRb.start_service(URI, [File, FileUtils])

    # client.rb
    module Sudo
    File, FileUtils = DRbObject.new_with_uri(SERVER_URI)
    end

    but, again, It doesn't work:

    client.rb:8:in `<module:Sudo>': can't convert DRb::DRbObject to Array
    (DRb::DRbObject#to_ary gives DRb::DRbUnknown) (TypeError)
    from client.rb:7:in `<main>'

    So I am compelled to run several DRb server instances?

    > That should operate as expected. Beyond that, you would need to use
    > ParseTree or ripper to extract the Ruby code you want executed on the
    > remote
    > VM or something like that, but then you need to ensure that all the
    > classes/objects it's using are actually loaded on the new VM.


    I see... looks like *a lot* of work...

    > For practicality's sake I'd suggest exposing a small DSL for doing what
    > you
    > want to do as root. FileUtils provides everything I'd think you need,
    > but
    > perhaps you have a use case I'm not envisioning.


    As a more flexible alternative, you should be able to say if you want to
    Sudo-ize FileUtils or other modules/classes.

    A possible API might look like this:

    Sudo.autoload :MyClass, 'mygem/myclass'
    Sudo.require 'fileutils'
    Sudo.enable :File, :FileUtils, :MyClass

    my_super_object = Sudo::MyClass.new

    Sudo::File.open ...

    Sudo::FileUtils.cp

    So you use superuser powers only explicitly when you really need them.

    --
    Posted via http://www.ruby-forum.com/.
    Guido De Rosa, Oct 19, 2010
    #10
  11. Guido De Rosa wrote in post #955389:
    > File, FileUtils = DRbObject.new_with_uri(SERVER_URI)
    > end
    >
    > but, again, It doesn't work:
    >
    > client.rb:8:in `<module:Sudo>': can't convert DRb::DRbObject to Array
    > (DRb::DRbObject#to_ary gives DRb::DRbUnknown) (TypeError)
    > from client.rb:7:in `<main>'


    That's just a side-effect of the multiple-assignment syntax (implicit
    splat), which only works on real Arrays. Try instead:

    front = DRbObject.new(...)
    File = front[0]
    FileUtils = front[1]

    Of course, you better be damned sure that your root DRb server is only
    accessible by trusted processes; by default, any user on your machine
    will be able to connect to it. (That's the reason I'd prefer to talk to
    the trusted process via a private pipe)

    If you are sure you want a root DRb server, I'd be inclined to write one
    which exposes a limited set of methods and sanitises their arguments
    before doing anything with them (and possibly also requires
    authentication) - rather than giving carte-blanche access to File and
    FileUtils.

    If you are running on a Unix system, then another option you have is to
    open a file descriptor in one (trusted) process and pass that open file
    descriptor across a socket. That avoids having DRb proxy objects at all.
    Have a look at snailgun if you want some sample code which does that;
    grep for send_io and recv_io.

    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Oct 20, 2010
    #11
  12. Brian Candler wrote in post #955751:
    > Guido De Rosa wrote in post #955389:
    >> File, FileUtils = DRbObject.new_with_uri(SERVER_URI)
    >> end
    >>
    >> but, again, It doesn't work:
    >>
    >> client.rb:8:in `<module:Sudo>': can't convert DRb::DRbObject to Array
    >> (DRb::DRbObject#to_ary gives DRb::DRbUnknown) (TypeError)
    >> from client.rb:7:in `<main>'

    >
    > That's just a side-effect of the multiple-assignment syntax (implicit
    > splat), which only works on real Arrays. Try instead:
    >
    > front = DRbObject.new(...)
    > File = front[0]
    > FileUtils = front[1]


    Yep. Thanks :)

    > Of course, you better be damned sure that your root DRb server is only
    > accessible by trusted processes; by default, any user on your machine
    > will be able to connect to it. (That's the reason I'd prefer to talk to
    > the trusted process via a private pipe)


    Yeah, nothing beats the security of anonymous, private pipe... Anyhow, I
    set permissions of UNIX socket:

    http://github.com/gderosa/rubysu/blob/5fab1503fdaac85cb3876b76cd16e3422e83df73/libexec/server.rb#L13

    Moreover, I don't keep a SUID daemon running; instead my approach is
    based on starting a DRb server on demand and kill it as soon as it's no
    longer required.

    This is not efficient, but imho there are no performance concerns here:
    becoming root is something you do occasionally, this is not the
    bottleneck.

    The usage would look like this:

    Sudo::Wrapper.new do |su|
    # a sudoed DRb daemon is started under the hood...

    puts su[File].read '/etc/shadow' # only readable by root
    # ...

    end # the daemon is killed

    Anyway, if you need a long running thing:

    su = Sudo::Wrapper.new

    su[an_object].method # acts as root

    # ...

    # ...

    su.close

    > If you are sure you want a root DRb server, I'd be inclined to write one
    > which exposes a limited set of methods and sanitises their arguments
    > before doing anything with them (and possibly also requires
    > authentication) - rather than giving carte-blanche access to File and
    > FileUtils.


    See above but, yes, there's a lot of work still TODO.

    > If you are running on a Unix system, then another option you have is to
    > open a file descriptor in one (trusted) process and pass that open file
    > descriptor across a socket. That avoids having DRb proxy objects at all.
    > Have a look at snailgun if you want some sample code which does that;
    > grep for send_io and recv_io.


    Very interesting, thanks! And I certainly need to study Unix IPC deeper
    and deeper... :)

    --
    Posted via http://www.ruby-forum.com/.
    Guido De Rosa, Oct 20, 2010
    #12
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Showjumper
    Replies:
    1
    Views:
    699
    Showjumper
    Mar 19, 2005
  2. Noozer

    Block DIV within a block DIV?

    Noozer, Jan 6, 2005, in forum: HTML
    Replies:
    3
    Views:
    11,362
    Mitja
    Jan 6, 2005
  3. Andy
    Replies:
    0
    Views:
    530
  4. morrell
    Replies:
    1
    Views:
    946
    roy axenov
    Oct 10, 2006
  5. Alvin
    Replies:
    8
    Views:
    968
Loading...

Share This Page