Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you prompt for a sudo password using Ruby?

Often I find myself needing to write scripts that have to execute some portions as a normal user and other portions as a super user. I am aware of one similar question on SO where the answer was to run the same script twice and execute it as sudo, however that is not sufficient for me. Some times I need to revert to being a normal user after a sudo operation.

I have written the following in Ruby to do this

#!/usr/bin/ruby
require 'rubygems'
require 'highline/import'
require 'pty'
require 'expect'

def sudorun(command, password)
  `sudo -k`
  PTY.spawn("sleep 1; sudo -u root #{command} 2>&1") { | stdin, stdout, pid |
  begin
    stdin.expect(/password/) {
    stdout.write("#{password}\n")
    puts stdin.read.lstrip
                              }
  rescue Errno::EIO
  end
 }
end

Unfortunately, using that code if the user enters the wrong password the script crashes. Ideally it should give the user 3 tries to get the sudo password right. How do I fix this?

I am running this on Linux Ubuntu BTW.

like image 296
Bernard Igiri Avatar asked Sep 19 '12 16:09

Bernard Igiri


People also ask

How do I force sudo to ask for password?

If your timestamp_timeout is zero, sudo always prompts for a password. This feature can be enabled only by the superuser, however. Ordinary users can achieve the same behavior with sudo -k, which forces sudo to prompt for a password on your next sudo command.

How set sudo password in terminal?

Option 2: Change sudo Password with the passwd Command First, open the terminal (CTRL+ALT+T). Type in your current password and hit Enter. The output you receive should show that you can now run commands as root. Type and retype a new password to verify the change.

Does sudo su ask for password?

By default, sudo needs that a user authenticates using a password before running a command. Some times you may need to run a command with root privileges, but you do not want to type a password using sudo command. This is useful for scripting or any other purpose.


2 Answers

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.

like image 79
Catnapper Avatar answered Nov 06 '22 22:11

Catnapper


If it is possible, you could move the stuff you want executed as root to a seperate file and use the system() function to run it as sudo, including the sudo prompt etc:

system("sudo ruby stufftorunasroot.rb")

The system() function is blocking, so the flow of your program doesn't need to be changed.

like image 5
zeebonk Avatar answered Nov 06 '22 23:11

zeebonk