Bash – Shell/Bash script: Properly drop privileges

bashprivilegeszsh

Okay, first of all, I know this question has been asked a few times here, but the answers given did not entirely satisfy me.
The situation is the following: I have written many scripts to automate various maintenance tasks, with the aim to be fully automatic and requiring not interaction at all once started.
Now often root privileges are needed for various tasks (like mounting stuff etc.) but much more often I do not like commands or entire blocks to be executed with superuser rights (like wget, or extracting an archive fetched from the web etc.), to limit damage in the worst case.
So obviously to satisfy my aims the script has to be called using sudo or su and drops privileges and regains them, depending on the commands.

Now I have considered a few options but they did not satisfy me entirely and therefore I would like to consult with experts here, to know which would be the best solution (with the aim that the code is easily understandable and logically structured for others as well once published):

1.) Factor out the bits to execute as non root user into an external file and execute that file from the main script using su <user> -c /path/to/file.
That methoed however has a couple of disadvantages: First of all it ruins the logical structure of the script and makes things harder to understand and to read, as well as creating problems with defining and using variables.

2.) Define an alias or a function to shorten su <user> -c "command". This is a really bad idea as 80% of the scripts would have that privilege prefix. Apart from that, multi-line commands are problematic (like if structures that contain commands that I do not want to run as root).

3.) Something like su <user> << EOF << ... EOF which would properly handle entire blocks of code. This would be the most elegant solution (because the logical structure of the script is not destroyed) if there was not one problem that bugs me: Syntax highlighting. All major editors I tried use one color for the entire block inside the here doc. It is not exactly nice to read through a script where 80% have no syntax highlighting.

Is there no really elegant way to handle this issue? Something like su <user> -c ( ...) without quotes would be cool. Or should I maybe switch to another shell? (right now I use bash, I heard zsh has a feature that allows setting EUID variables or something like that?)
If there is no elegant solution, which approach would be preferable?

Best Answer

With zsh, if running as root and you set

EUID=1000

It will set the euid to 1000. The real uid will still be set to 0, so you can go back to EUID 0. Any program that you run would also be able to regain privileges by doing a setuid() but if the intent is to prevent unintentional damage as opposed to guard against malicious software, then that should be enough.

#! /bin/zsh -
GID=1000 # optional
EUID=1000 # drop *effective* privileges

some-potentially-harmful-command-that-doesnt-need-superuser

and-another

EUID=0 some-command-that-needs-superuser

more-non-privileged-commands

EUID=0 UID=1000 # now drop privileges with no coming back 
# alternatively:
# USERNAME=stephane # sets uid, gid, supplementary groups... based on the user db

Note that you can set EUID/UID... for the duration of a command with

UID=1000 the-command

But not UID or USERNAME if the-command is builtin or a function as zsh would not fork a different process so would not be able to restore the real userid.

You can also set EUID/UID/USERNAME in a subshell with:

(EUID=0 USERNAME=stephane; untrusted-application)

(we set EUID to 0 to be able to switch user afterwards).