How to Prompt for a Sudo Password Using Ruby

How do you prompt for a sudo password using Ruby?

In my opinion, running a script that does stuff internally with sudo is wrong. A better approach is to have the user run the whole script with sudo, and have the script fork lesser-privileged children to do stuff:

# Drops privileges to that of the specified user
def drop_priv user
Process.initgroups(user.username, user.gid)
Process::Sys.setegid(user.gid)
Process::Sys.setgid(user.gid)
Process::Sys.setuid(user.uid)
end

# Execute the provided block in a child process as the specified user
# The parent blocks until the child finishes.
def do_as_user user
unless pid = fork
drop_priv(user)
yield if block_given?
exit! 0 # prevent remainder of script from running in the child process
end
puts "Child running as PID #{pid} with reduced privs"
Process.wait(pid)
end

at_exit { puts 'Script finished.' }

User = Struct.new(:username, :uid, :gid)
user = User.new('nobody', 65534, 65534)

do_as_user(user) do
sleep 1 # do something more useful here
exit! 2 # optionally provide an exit code
end

puts "Child exited with status #{$?.exitstatus}"
puts 'Running stuff as root'
sleep 1

do_as_user(user) do
puts 'Doing stuff as a user'
sleep 1
end

This example script has two helper methods. #drop_priv takes an object with username, uid, and gid defined and properly reduces the permissions of the executing process. The #do_as_user method calls #drop_priv in a child process before yielding to the provided block. Note the use of #exit! to prevent the child from running any part of the script outside of the block while avoiding the at_exit hook.

Often overlooked security concerns to think about:

  • Inheritance of open file descriptors
  • Environment variable filtering
  • Run children in a chroot?

Depending on what the script is doing, any of these may need to be addressed. #drop_priv is an ideal place to handle all of them.

Ruby CLI: Prompt for root password and continue executing script as root?

This works. It just uses exec to call itself (test_script in this case) again. But be very careful to make sure that it doesn't run infinitely by adding a condition which will call exit.

#!/usr/bin/env ruby

if ARGV[0] == "--second"
puts "...called again and exiting."
exit
end

puts "Calling self again..."
exec "sudo ./test_script --second"

Passing variable from Ruby as a password for shell

For security reasons, sudo doesn't accept passwords on standard input by default. You should configure your sudoers file with a NOPASSWD: tag for the commands you want to execute without prompting, and invoke sudo with the -n flag to ensure that your script doesn't get hung up waiting for input.

If you insist on passing in passwords, see if your sudo supports the -S flag, which (on my system) says:

   -S          The -S (stdin) option causes sudo to read the password from
the standard input instead of the terminal device. The
password must be followed by a newline character.

How to net-ssh sudo su in Ruby

sudo supports the -c option, which passes a command to the sub-shell. Here are some of the sudo flags that might be useful to you:

-c, --command=COMMAND
pass a single COMMAND to the shell with -c

--session-command=COMMAND
pass a single COMMAND to the shell with -c and do not create a new session

-m, --preserve-environment
do not reset environment variables

-s, --shell=SHELL
run SHELL if /etc/shells allows it

So, using something like sudo su someuser -c 'ls;date', you'll execute the commands ls and date as someuser. Give it a try at the command-line on that host to get a feel for what you can do, then apply it to your SSH session.

See man sudo for more information.

Also, just as a coding tip, you can reduce:

if data =~ /\[sudo\]/ || data =~ /Password/i

to:

if (data[/\[sudo\]|Password/i])


Related Topics



Leave a reply



Submit