33

I have a script which can run as sudo script.sh or pkexec script.sh

It would be much nicer from the user point of view if the script asked for the password from the user when just running it by name script.sh.

How can I "embed" request to pkexec or sudo to run the whole script with root privilege?

Note that running everything with sudo sh -c might not be the best solution as I have functions in the script.

Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497

6 Answers6

73

This'll work:

echo "$(whoami)"

[ "$UID" -eq 0 ] || exec sudo "$0" "$@"

example:

./test.sh 
blade
[sudo] password for blade: 
root
muru
  • 197,895
  • 55
  • 485
  • 740
blade19899
  • 26,704
  • 4
    What's good about this is recursion with exec - call itself but at the same time replacing process , so we don't spawn multiple instances of the script – Sergiy Kolodyazhnyy Mar 15 '16 at 16:54
  • 2
    Recursion to the rescue!!! Remind me to add a bounty to this one in a few days! – Fabby Mar 16 '16 at 00:30
  • 7
    Something to be aware of when doing this is privilege escalation; basically, the more commands you run as root, the more opportunities there are for a malicious hacker to exploit something to get access to a root account. I'm not saying it's always wrong to make the whole script run as root, but it's worth putting a bit of thought into it before you decide to do so. I think it would be nice (though not necessary) if the answer would touch on that. – David Z Mar 16 '16 at 08:44
  • If you have a decent timeout value set in /etc/sudoers, then you can optionally put sudo in front of each command in the script which really needs root access and it will only ask once. One thing I often mess up is that calling such a script from a gui won't work because sudo gets /dev/null for password input and fails. That's what gksudo and kdesudo were designed to handle because they'll use the gui to ask for the password. – Joe Mar 16 '16 at 18:08
  • 1
  • this is bad because it is running every command in the script with sudo. It is better to isolate the few commands that really need sudo and add them where necessary. Don't include the evaluation of functions in these sudo calls; they should be as clear and minimal as possible. – Douglas Held Mar 17 '16 at 11:23
16

blade19899's answer is indeed the way to go, however one could also call sudo bash in the shebang:

#!/usr/bin/sudo bash
# ...

The obvious caveat is this will work only as long as the script is called with ./script and will fail as soon as the script is called with bash script.

kos
  • 35,891
  • 6
    This is one of the reasons why you should never type bash script on the command line to invoke a script. If the script specifies anything other than bash in the #! line, then invoking it with bash script is going to fail. If I tried to invoke a python script using bash script.py it would also fail. – kasperd Mar 16 '16 at 12:48
  • 1
    You can run it with perl script, though. Perl, in an effort to be really really nice, checks the shebang line, and if it's not invoking perl it runs the correct program. – tbodt Mar 16 '16 at 22:23
  • 1
    This is bad because it is running every command in the script with sudo. It is better to isolate the few commands that really need sudo and add them where necessary. Don't include the evaluation of functions in these sudo calls; they should be as clear and minimal as possible. – Douglas Held Mar 17 '16 at 11:23
  • @DouglasHeld While I may agree with that, this is what the question is asking for: "How can I "embed" request to pkexec or sudo to run the whole script with root privilege?". Pretty sure there would be cases of use in which being able to do this would be useful. – kos Mar 17 '16 at 11:33
15

If you'd like a pretty dialog, try something like this. I ripped this straight out of something else I wrote, so it's got extra stuff you might not need or want, but it shows the general idea:

brand="My Software"

# Check that the script is running as root. If not, then prompt for the sudo
# password and re-execute this script with sudo.
if [ "$(id -nu)" != "root" ]; then
    sudo -k
    pass=$(whiptail --backtitle "$brand Installer" --title "Authentication required" --passwordbox "Installing $brand requires administrative privilege. Please authenticate to begin the installation.\n\n[sudo] Password for user $USER:" 12 50 3>&2 2>&1 1>&3-)
    exec sudo -S -p '' "$0" "$@" <<< "$pass"
    exit 1
fi

sudo dialog

This uses whiptail, which you can install if you don't already have it:

sudo apt-get install whiptail
  • 1
    Why are you saving the password in a variable? – heemayl Mar 16 '16 at 03:52
  • 11
    Don't call exec echo [PASSWORD]! It'll spawn a process with the password on the command-line that any user can see. Either use the built-in command echo (force the built-in version with builtin echo) or the bashism <<< (like so: sudo ... <<< "$pass"); Sh and other shells have Here Documents for these cases. Also, quote the password in case it contains special characters. – David Foerster Mar 16 '16 at 03:58
  • Alternatively, put the whiptail command into a separate script and use something like SUDO_ASKPASS=/path/to/askpass-with-whiptail.sh sudo -A -p "My password prompt" -- "$0" "$@" to have sudo deal with the custom password prompt. – David Foerster Mar 16 '16 at 04:12
  • @DavidFoerster That could work, but then you either need two scripts, or you write your askpass script to a temporary file and then pass it to sudo. – Michael Hampton Mar 16 '16 at 04:15
  • Ok however others already noted it is undesirable to pass password to a variable. The way id approach this is to pass output of dialog to named pipe as i have shown here http://askubuntu.com/a/704643/295286 – Sergiy Kolodyazhnyy Mar 16 '16 at 06:50
  • @Serg Eh? What's the problem with having a password in a variable? – Michael Hampton Mar 16 '16 at 07:02
  • @MichaelHampton standard practice ; avoiding potential for the password being stolen – Sergiy Kolodyazhnyy Mar 16 '16 at 07:06
  • @Serg By whom? root?!? You have to do a cold boot attack, or at least break root, in order to dig a user password out of the bowels of free memory? Which will get you the root access you already broke? There are places where this level of paranoia might be justified, but very few of us will ever work in one. It would be nice if bash had a way to clear the RAM used by a variable, but it doesn't as far as I know. If this is a requirement in your environment, you shouldn't be running this sort of script at all. – Michael Hampton Mar 16 '16 at 07:09
  • @Serg And on the same line, if you use a named pipe you're just copying the password to a whole bunch of different places in memory, which makes this (minuscule) attack surface larger (though still tiny). – Michael Hampton Mar 16 '16 at 07:16
  • @MichaelHampton why get root if one can get another user's sudo password ? Named pipe may be in memory, but it sure can be overwritten and released to /dev/null , can't it ? I may not be a security expert, but if it has been a convention in the *nix world for long time, hey - maybe there's a reason for that. – Sergiy Kolodyazhnyy Mar 16 '16 at 07:28
  • @Serg You have to get root in order to read /dev/mem to find the user's password in free memory and hope you find it before the kernel zeroes it out and hands the freshly sanitized memory to another process... How do you expect to read free memory without being root?! – Michael Hampton Mar 16 '16 at 07:32
8

It appears that nobody else has addressed the obvious concern here. Putting sudo within your script that you then distribute promotes bad user habits. (I'm assuming you're distributing it because you mention "from a user point of view.")

The truth is that there is a guideline in using applications and scripts which is similar to the security principle in banking of: Never give out your personal information to someone who calls you and says they're calling "from your bank", and which exists for similar reasons.

The rule for applications is:

Never type in your password when prompted unless you are certain what is being done with it. This applies triply to anyone with sudo access.

If you're typing your password in because you ran sudo on the command line, great. If you're typing it in because you ran an SSH command, fine. If you're typing it in when you log in to your computer, great, of course.

If you just run a foreign script or executable and tamely enter your password when prompted for it, you have no idea what the script is doing with it. It could be storing it in a temp file in plaintext, for all you know, and might even fail to clean up after itself.

Obviously there are separate and additional concerns about running an unknown set of commands as root, but what I'm talking about here is maintaining security on the password itself. Even assuming the application/script is not malicious, you still want your password to be handled securely to prevent other applications from getting hold of it and using it maliciously.

So, my own personal response to this is, the best thing to put in your script if it needs root privileges, is:

#!/bin/bash
[ "$UID" -eq 0 ] || { echo "This script must be run as root."; exit 1;}

# do privileged stuff, etc.
Wildcard
  • 1,153
  • While I completely agree with you, a user who runs something without knowing what it does as sudo script_name is just as vulnerable, it's the same. Maybe this idea does promote bad habits - I won't say anything about that. But the key is knowing what a program does, and that's user's responsibility. That's the whole idea behind open-source software. As for my own script, well . . . a script is plain text - users can read it if they wanna know what it does – Sergiy Kolodyazhnyy Mar 21 '16 at 09:34
  • 1
    @SergiyKolodyazhnyy it’s similar, but if you type your password directly into the script it’s actually worse. A script run with sudo still doesn’t know your password. – Wildcard May 25 '19 at 21:57
7

I preface the commands within the script which need root access with sudo - if the user has not already gained permissions, the script prompts for a password at that point.

example

#!/bin/sh 
mem=$(free  | awk '/Mem:/ {print $4}')
swap=$(free | awk '/Swap:/ {print $3}')

if [ $mem -lt $swap ]; then
    echo "ERROR: not enough RAM to write swap back, nothing done" >&2
    exit 1
fi

sudo swapoff -a && 
sudo swapon -a

This script can either be run as sudo <scriptname> or as <scriptname>. In either case it will ask for the password, only once.

Charles Green
  • 21,339
  • 3
    The issue here is that there may be multiple commands that need root access, which means calling sudo for 25 different commands redundant - it's easier to call sudo once. Here however, the reason your script calls sudo only once is because sudo has time out of 15 minutes - commands that take longer than that will need to be reprompted. Your way works just . . . not the way I need it to work. – Sergiy Kolodyazhnyy Mar 15 '16 at 16:41
  • @Serg For a small script, the way I had was quick and easy - I'm truly not an elegant programmer! – Charles Green Mar 16 '16 at 19:28
  • You are the best programmer posting on this page. You are using sudo exactly as it should be used - only where needed and with very clear results expected. – Douglas Held Mar 17 '16 at 11:26
1

I did it this way:

echo -n "Enter password for sudo rights: "
read -s pass

echo $pass | sudo -S [your command here]
88weighed
  • 690