2

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?

James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 1
    I'm sitting on the fence here. While it is on the one hand a good idea to execute only that part of the script as root which needs root permissions, I don't like the idea of doing something implicitly with root permissions - possibly without the user being aware of it on the other hand. (for example if sudo is configured to work without a password). Probably the cleanest solution would be to required root permssions for the whole script and drop privileges for actions which doesn't require them. – hek2mgl Dec 27 '15 at 10:59
  • 1
    @hek2mgl . "Yes and..." The other thing is that not everything needs to be run with **root** privileges. Unix provides *privilege separation* through users, and these days applications all get installed with their own users so they don't *need* to run as root. I'm a big fan of using sudo to allow only what's required. If you `sudo -s` on one of my systems, I will get an email, and you will get a stern chat. – ghoti Dec 27 '15 at 15:51

3 Answers3

4

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] ...
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
2

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.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 2
    I like this for very simple sudo use, but it doesn't work if sudo has been configured with any level of sophistication. Try setting up a NOPASSWD entry for just your user for just one command. `keith ALL=(ALL) NOPASSWD: /usr/bin/whoami` for example. `sudo -nv` will return true, but you still can't `sudo -s` successfully, or run any other commands. – ghoti Dec 27 '15 at 04:02
2

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.

ghoti
  • 45,319
  • 8
  • 65
  • 104