开发者

Run Ruby block as specific OS user?

Can you execute a block of Ruby code as a different OS user?

What I, ideally, want is something like this:

user("christoffer") do
  # do somethi开发者_Python百科ng
end

Possible?


This code can do what you want. Error handling is up to you. ;-)

require 'etc'

def as_user(user, &block)
    u = Etc.getpwnam(user)
    Process.fork do
        Process.uid = u.uid
        block.call(user)
    end
end

puts("caller PID = #{Process.pid}")
puts("caller UID = #{Process.uid}")
as_user "bmc" do |user|
    puts("In block as #{user} (uid=#{Process.uid}), pid is #{Process.pid}")
end

Note, however, that it will require that you run Ruby as root, or as setuid-to-root, which has some severe security implications.


The accepted answer does change UID, but doing this alone can have surprising results when you create files or child processes. Try:

as_user 'bmc' do |user|
  File.open('/tmp/out.txt', 'w')
end

You'll find that file was created as root, which isn't what one might expect.

The behavior is less predictable when running a command using the backtics. The results of the following probably aren't what one would expect:

as_user 'puppet' do
  puts `whoami`
  puts `id`
  puts `whoami; id`
end

Testing on a Linux system, the first puts printed root. id printed the following:

uid=1052(chet) gid=0(root) euid=0(root) groups=0(root)

The final puts disagreed:

puppet
uid=1052(chet) gid=0(root) groups=0(root)

To get consistent behavior, be sure to set effective UID as well:

def as_user(user, &block)
    u = Etc.getpwnam(user)
    Process.fork do
        Process.uid = Process.euid = u.uid
        block.call(user)
     end
end

It can be useful to get a value back from the child process. Adding a little IPC fun gives:

require 'etc'

def as_user(user, &block)
  u = (user.is_a? Integer) ? Etc.getpwuid(user) : Etc.getpwnam(user)

  reader, writer = IO.pipe

  Process.fork do
    # the child process won't need to read from the pipe
    reader.close

    # use primary group ID of target user
    # This needs to be done first as we won't have
    # permission to change our group after changing EUID
    Process.gid = Process.egid = u.gid

    # set real and effective UIDs to target user
    Process.uid = Process.euid = u.uid

    # get the result and write it to the IPC pipe
    result = block.call(user)
    Marshal.dump(result, writer)
    writer.close

    # prevent shutdown hooks from running
    Process.exit!(true)
  end

  # back to reality... we won't be writing anything
  writer.close

  # block until there's data to read
  result = Marshal.load(reader)

  # done with that!
  reader.close

  # return block result
  result
end

val = as_user 'chet' do
  `whoami` + `id` + `whoami; id`
end

puts "back from wonderland: #{val}"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜