Recipe 23.3. Running Code as Another User
Problem
While writing a Ruby script that runs as
root, you need to take some action on behalf of another user: say, run an external program or create a file.
Solution
Simply set Process.euid to the UID of the user. When you're done, set it back to its previous value (that is,
root's UID). Here's a method Process.as_uid that runs a code block under a different user ID and resets it at the end:
module Process
def as_uid(uid)
old_euid, old_uid = Process.euid, Process.uid
Process.euid, Process.uid = uid, uid
begin
yield
ensure
Process.euid, Process.uid = old_euid, old_uid
end
end
module_function(:as_uid)
end
Discussion
When a Unix process tries to do something that requires special permissions (like access a file), the permissions are checked according to the "effective user ID" of the process. The effective user ID starts out as the user ID you used when you started the process, but if you're
root you can change the effective user ID with Process.euid=. The operating system will treat you as though you were really that user.
This comes in handy when you're administering a system used by others. When someone asks you for help, you can write a script that impersonates them and runs the commands they don't know how to run. Rather than creating files as
root and using chown to give them to another user, you can create the files as the other user in the first place.
Here's an example. On my system the account leonardr has UID 1000. When run as root, this code will create one directory owned by root and one owned by leonardr:
Dir.mkdir("as_root")
Process.as_uid(1000) do
Dir.mkdir("as_leonardr")
%x{whoami}
end
# => "leonardr\n"
Here are the directories:
$ ls -ld as_*
drwxr-xr-x 2 leonardr root 4096 Feb 2 13:06 as_leonardr/
drwxr-xr-x 2 root root 4096 Feb 2 13:06 as_root/
When you're impersonating another user, your permissions are restricted to what that user can do. I can't remove the as_root directory as a nonroot user, because I created it as root:
Process.as_uid(1000) do
Dir.rmdir("as_root")
end
# Errno::EPERM: Operation not permitted - as_root
Dir.rmdir("as_root") # => 0
On Windows, you can do something like this by splitting your Ruby script into two, and
running the second one through runas.exe:
# script_one.rb
system 'runas /user:frednerk ruby script_two.rb'
See Also
Recipe 6.2, "Checking Your Access to a File" If you want to pass in the name of the user to impersonate, instead of their UID, you can adapt the technique shown in Recipe 23.10, "Killing All Processes for a Given User"
|