Shell scripting - authenticate once for many privilege escalations - bash

Suppose I have a script commands.sh that looks something like the following:
command1 # Must be run as root
command2 # Absolutely cannot be run as root
command3 # Should be run as unprivileged user, but can be run as root
...
...
If we run commands.sh, then command1 will complain. If we run sudo commands.sh, then command2 will complain. We could edit commands.sh to look like the following,
sudo command1
command2
command3
...
...
That is, inserting sudos in the appropriate places. With the default sudo config (15 minute authentication persistence), this works great (assuming the script takes less than 15 minutes to run). However, if the script is longer, or we have authentication persistence disabled, then I feel there's not an obvious way forward. Additionally, some systems might not even use sudo to authenticate - they may use doas or something else instead.
Is there any (POSIX-y?) way of creating a script, authenticating once at the start, and then persisting that authentication through the script when necessary, without the use of sudo's persistence?

One possibility is to require the script to be run via sudo, but then use sudo -u within the script to revert (/demote) to the original user for commands that shouldn't be run as root. It'd probably be a good idea to include some sanity-checking at the beginning of the script to make sure it's being run from the right environment:
#!/bin/bash
if [[ "$EUID" != 0 ]]; then
# The script is not running as root
echo "This script must be run via sudo (as root)" >&2
exit 1
# Optional: replace exit 1 with:
# exec sudo "$0" "$#" # Re-run this script via sudo
# exit $? # In case the exec fails
elif [[ -z "$SUDO_USER" ]]; then
# The script is running as root, but doesn't have $SUDO_USER
# (the original user who ran sudo) to revert back to for
# unprivileged operations
echo "This script must be run via sudo, not directly as root" >&2
exit 1
elif [[ "$SUDO_USER" = root || "$SUDO_UID" = 0 ]]; then
# The script is running as root via sudo, but $SUDO_USER is
# *also* root, so is not suitable to revert back to for
# unprivileged operations
echo "This script must be run via sudo, from a regular (non-root) account" >&2
exit 1
fi
command1 # Must be run as root
sudo -u "$SUDO_USER" command2 # Absolutely cannot be run as root
sudo -u "$SUDO_USER" command3 # Should be run as unprivileged user, but can be run as root

Related

How can I request elevated permissions in a bash script's begin and let it go at the end?

I have a script (myscript.sh) which runs a few commands which need elevated privileges (i.e. needs to run with sudo).
Script is quite complex, but to demonstrate it is like below:
#!/bin/bash
echo "hello"
command1_which_needs_sudo
echo "hello2"
command2_which_needs_sudo
echo "hello3"
...
If I run it as a normal user without the required privileges:
$ ./myscript.sh
hello
must be super-user to perform this action
However if I run it with the correct privileges, it will work fine:
$ sudo ./myscript.sh
hello
hello2
hello3
Can I somehow achieve to run myscript.sh without sudo, and make the script requesting the elevated privileges only once in the beginning (and pass it back once it has finished)?
So obviously, sudo command1_which_needs_sudo will not be good, as command2 also need privileges.
How can I do this if I don't want to create another file, and due to script complexity I also don't want to do this with heredoc syntax?
If your main concern is code clarity, using wrapper functions can do a lot of good.
# call any named bash function under sudo with arbitrary arguments
run_escalated_function() {
local function_name args_q
function_name=$1; shift || return
printf -v args_q '%q ' "$#"
sudo bash -c "$(declare -f "$function_name"); $function_name $args_q"
}
privileged_bits() {
command1_which_needs_sudo
echo "hello2"
command2_which_needs_sudo
}
echo "hello"
run_escalated_function privileged_bits
echo "hello3"
If you want to run the script with root privileges without having to type sudo in the terminal nor having to type the password more than once then you can use:
#!/bin/bash
if [ "$EUID" -ne 0 ]
then
exec sudo -s "$0" "$#"
fi
echo "hello"
command1_which_needs_sudo
echo "hello2"
command2_which_needs_sudo
echo "hello3"
# ...
sudo -k
Update:
If your goal is to execute one part of the script with sudo rights then using a quoted here‑document is probably the easiest solution; there won't be any syntax issues because the current shell won't expand anything in it.
#!/bin/bash
echo "hello"
sudo -s var="hello2" <<'END_OF_SUDO'
command1_which_needs_sudo
echo "$var"
command2_which_needs_sudo
END_OF_SUDO
sudo -k
echo "hello3"
#...
remark: take notice that you can use external values in the here-document script by setting varname=value in the sudo command.

Allow user input in second command in bash pipe

I'm looking for how I might allow user input in a second command in a bash statement and I'm not sure how to go about it. I'd like to be able to provide a one-liner for someone to be able to install my application, but part of that application process requires asking some questions.
The current script setup looks like:
curl <url/to/bootstrap.sh> | bash
and then boostrap.sh does:
if [ $UID -ne 0 ]; then
echo "This script requires root to run. Restarting the script under root."
exec sudo $0 "$#"
exit $?
fi
git clone <url_to_repo> /usr/local/repo/
bash /usr/local/repo/.setup/install_system.sh
which in turn calls a python3 script that asks for input.
I know that the the curl in the first line is using stdin and so that might make what I'm asking impossible and that it has to be two lines to ever work:
wget <url/to/boostrap.sh>
bash bootstrap.sh
You can restructure your script to run this way:
bash -c "$(curl -s http://0.0.0.0//test.bash 2>/dev/null)"
foo
wololo:a
a
My test.bash is really just
#!/bin/bash
echo foo
python -c 'x = raw_input("wololo:");print(x)'`
To demonstrate that stdin can be read from in this way. Sure it creates a subshell to take care of curl but it allows you to keep reading from stdin as well.

Execute root command in shell script and change to normal user after a process

I am trying to create a shell script where it uses the root access to install all the dependencies and after completing it, it exits from the root command and continue executing the script as normal user.
This is the test code:
#!/bin/sh
output=$(whoami)
if [ "$(whoami)" != "root" ]
then
su -c "echo \"hi\""
echo $output
//continue doing some installtion
exit
fi
echo $output //this should show the normal username not the root name
#!/bin/sh
su -c 'echo $(whoami)'
echo $(whoami)
When you pass the command with su following with an option -c it runs as root user, so when you want to install any dependencies you can run the following command as shown in above example.

Want to find status of sudo su - in multiple servers

I have a bash script, hostlist file and an expect script:
Below is the bash script to take inputs from hostlist file and keep looping ssh for multiple servers.
for x in $(cat hostlist); do
./sudoscript.exp $x
done
Below is the expect cum bash script I want to tun and collect outputs of sudo su - command. I just need to get outputs as '0 or non zero values in a file for successful run/execution of 'sudo su - '. I just need to simulate the execution and check if the command runs successfully or not with out actually changing user to admin by doing sudo su -.
#!/bin/bash
#!/usr/bin/expect
spawn ssh [lindex $argv 0]
expect "$"
send "sudo su -\r" exit ; echo $server:$? >> output
Can someone please suggest to complete the script above.
What exactly are you trying to do?
Maybe you're trying to see if it is possible to become root without a password? If that's the case, try:
for x in $(cat hostlist); do
echo $x
ssh $x sudo -l |egrep -w 'NOPASSWD:.*(ALL|/su)'
echo
done
sudo -l will list what you can run. It requires your password unless you have one or more commands that do not require your password (ssh won't run interactively when called with a command and without the -t flag. This is intentional since we don't want that).
The egrep command limits the results to just what can be done without a password as well as either ALL commands or else su itself. (Note, this won't find su if it's in an alias.)

Execute Shell script without sudo password

I have a shell script as given below.
#!/bin/bash
sudo -u testuser -H sh -c "
mkdir /usr/local/testdir;
if [ $? -eq 0 ];then
echo "Successfull";
else
echo "Unsuccessfull";
fi
"
I have given privileges to user testuser to execute shell script with sudo, but without asking password.For this I add the below line in /etc/sudoers file,
testuser ALL=(ALL) NOPASSWD: ALL
And it works fine that, I could run commands with sudo, but without asking password. But the above shell script always giving out put ass follows,
mkdir: cannot create directory `/usr/local/testdir': Permission denied
Successfull
And it is not creating directory testdir inside /usr/local. Please advice me what modification shall I need to do to work this script fine.
Thanks.
Two problems:
1.) You told:
sudo -u testuser -H ...
what's mean: run the command as testuser, and he doesn't has permissions to write into the /usr/local therefore you getting permission denied.
When you remove the -u testuser, the command will run as root (as default) (without password for the testuser) and will create the directory.
Seems, you simply misunderstand how the sudo and /etc/sudoers works. The -u user mean
-u user' The -u (user) option causes sudo to run the specified command as a user
other than root. To specify a uid instead of a user name, #uid.
When running commands as a uid, many shells require that the '#' be escaped with a
backslash ('\'). Security policies may restrict uids
to those listed in the password database. The sudoers policy allows
uids that are not in the password database as long as the targetpw
option is not set. Other security policies may not support this.
2.) second problem the Successfull message.
You're using double quotes for sh -c. The Variable expansion is done BEFORE the sh -c even starts. So use single quotes, and will get the correct Unsuccessfull message:
sudo -u testuser -H sh -c '
mkdir /usr/local/testdir
if [ $? -eq 0 ];then
echo "Successfull";
else
echo "Unsuccessfull";
fi
'
and use the next as a solution:
sudo -H sh -c '
mkdir /usr/local/testdir
if [ $? -eq 0 ];then
echo "Successfull";
else
echo "Unsuccessfull";
fi
'

Resources