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}"
精彩评论