How to demand root privileges in a shell script? [duplicate]

Say you have a shell script that could potentially require root privileges. You don't want to force the user to run it as sudo. However, if it does require that privilege, you want to prompt the user to enter their password, rather than complaining and forcing them to re-enter the command with sudo.

How would you go about doing this in a Bash script? sudo true seems to work, but it feels like a hack. Is there a better way?

2

3 Answers

Here's what I often do. This is loose pseudo-code but should give you the idea:

# myscript -- possibly execute as sudo
if (passed -e option) then read variables from envfile
else ... need_root = ... # set variables ... if ($need_root && $uid != 0) then env [or whatever] > /tmp/envfile exec sudo myscript -e/tmp/envfile ... fi
fi
# stuff to execute as root [or not] ...

The command

sudo -nv

checks whether the user has current sudo credentials (-v), but will fail rather than prompting if access has expired (-n).

So this:

if sudo -nv 2>/dev/null && sudo -v ; then sudo whoami
else echo No access
fi

will check whether the user's sudo credentials are current, and prompt for a password only if they're not.

There is a possible race condition: the user's credentials could expire just after the check.

As ghoti points out in a comment, this may not work if the sudoers file is set up to allow only certain commands to be executed. For that and other reasons, be sure to check whether each sudo command succeeded or failed.

1

If your plan is to use sudo for privilege escalation, one wrinkle you may have to deal with is that that sudo can be set up to permit root access to some commands and not others. For example let's imagine you've got a server that runs VirtualBox, with different people managing the applications than are managing the OS. Your sudoers file might contain something like the following:

Cmnd_Alias SAFE = /bin/true, /bin/false, /usr/bin/id, /usr/bin/who*
Cmnd_Alias SHUTDOWN = /sbin/shutdown, /sbin/halt, /sbin/reboot
Cmnd_Alias SU = /bin/su, /usr/bin/vi*, /usr/sbin/visudo
Cmnd_Alias SHELLS = /bin/sh, /bin/bash, /bin/csh, /bin/tcsh
Cmnd_Alias VBOX = /usr/bin/VBoxManage
%wheel ALL=(ALL) ALL, !SU, !SHELLS, !SHUTDOWN
%staff ALL=(ALL) !SU, !SHELLS, NOPASSWD: SAFE
%operator ALL=(ALL) SAFE, SHUTDOWN
%vboxusers ALL=(ALL) NOPASSWD: VBOX

In this case, a member of the vboxusers unix group will always get a successful response to a sudo -nv, because of the existence of the NOPASSWD entry for that group. But a member of that group running any other command than VBoxManage will get a password challenge and a security warning.

So you need to determine whether the command you need to run can be run without a password prompt. If you don't know how sudo is configured on the system where your script is running, the canonical test is to run the command. Running sudo -nv will only tell you whether you are authenticated; it won't tell you what commands you have access to.


That said, if you can safely rely on a sudo configuration where, say, membership in wheel group gives you access to all commands, for example with:

%wheel ALL=(ALL) ALL

then you can use sudo -nv to test for escalation capabilities. But your script might have some things that it runs as root, and some things it doesn't. You might also want to consider other tools besides sudo for privilege escalation.

One strategy might be to set a variable to preface commands if the need is there, but leave the variable blank if you're already root (i.e. running the entire script inside sudo).

if ! which sudo >/dev/null 2>/dev/null; then PM_SU_CMD="su - root -c"
elif sudo -nv 2>/dev/null; then PM_SU_CMD="sudo"
else echo "ERROR: I can't get root." >&2 exit 1
fi

Of course, if we are already root, unset this to avoid potential conflict:

[ `ps -o uid= $$` -eq 0 ] && unset PM_SU_CMD

(Note that this is a query of the system's process table; we don't want to rely on the shell's environment, because that can be spoofed.)

Then, certain system utilities might be made more easily available using functions:

# Superuser versions for commands that need root privileges
find_s () { $PM_SU_CMD "/usr/bin/find $*"; }
mkdir_s () { $PM_SU_CMD "/bin/mkdir -p $1"; }
rm_s () { $PM_SU_CMD "/bin/rm $*"; }

Then within your script, you'd simply use the _s version of things that need to be run with root privileges.

This is of course by no means the only way to solve this problem.

You Might Also Like