What portable options do exist to drop root privileges and execute a given command as a different user from a shell script?
After doing a bit of research, here are a few non-options:
su $USER -c "$COMMAND" uses the PAM stack and creates a new cgroup (when run under systemd). It also fails in user namespaces, because the audit call returns -EPERM on older versions of Linux.
sudo -u $USER $COMMAND is not installed by default on many systems.
start-stop-daemon --pidfile /dev/null --start --chuid $USER --startas /bin/sh -- -c "$COMMAND" is very hard to use and only available on Debian systems.
chpst -u $USER $COMMAND is missing on many systems.
runuser -u $USER -- $COMMAND works where su doesn't, but requires recent util-linux.
If it's POSIX you want, then su is your only option (unless you want to write a C program). su has several advantages (or not, depending on your requirements):
It's a system tool which isn't going to forget about the new coffee UID introduced in Linux 3.42 (the UID for beverage drinking purposes) and which isn't going to goof by dropping user privileges before group privileges or to forget about capabilities.
It sets privileges to a known state: a user ID, that user's recorded group(s) from the user and group databases, no extra capabilities.
It records log entries.
And, again, it's completely standard, guaranteed to be available everywhere but on the most broken systems.
Now in practice some systems aren't POSIX — like this older Linux where it fails in user namespaces. Them's the breaks.
If you want something that's reasonably portable in practice (on non-embedded platforms) and that gives you a greater decree of control, use Perl (or Python, a bit less commonly installed). For preference, use a solid module: Privilege::Drop.
perl -e 'use Privileges::Drop; drop_uid_gid(123, 456); exec("/path/to/command", "--option", "an argument")'
Privilege::Drop takes care of doing things right (dropping supplemental groups, checking for errors). It might not be complete, however; for example it isn't aware of capabilities.
If you must do it by hand, take care of several things:
Drop group privileges before user privileges.
To drop supplemental groups, set $) = "456 456" where 456 is the target GID ($) = 456 would only set the EGID without affecting the supplemental groups).
Check the (E)[UG]ID afterwards and abort on failure.
For folks who are reading this in a context where the relevant meaning of "POSIX shell script" is "POSIX sh script, being run on an arbitrary Linux system", as opposed to "sh script, run on a system guaranteed to have only tools guaranteed by POSIX", there are more options available.
Borrowing from an excellent answer to the UNIX & Linux question How do I drop root privileges in shell scripts:
Modern util-linux has setpriv, which can be used in the manner of:
setpriv --reuid=user --regid=group --init-groups --inh-caps=-all yourcommand
The excellent article by #JdeBP Don't Abuse su For Dropping Privileges is also worth reading.
Related
I have inherited an application which is written in dozens (maybe hundreds, I haven't counted exactly) of PostgreSQL functions. In order to check the application code into git and be able to easily work on specific functions, I used pg_extractor to export the database into a separate file for each function.
In order to easily apply updates from git (both on developer machines and in production), I wrote a bash script that uses the psql command line client to run all of the function files, which causes the database server to be updated to match the files from git.
The gist of it looks like this (with some initialization code replaced by comments for brevity):
#!/bin/bash
# Check if a .env file is present and load it to set the PGHOST, PGPORT, PGUSER, PGPASSWORD, and PGDATABASE environment variables
# Check that `psql` is on the PATH
# Check the the database set in PGDATABASE exists on the server
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
makeFunction () {
echo -n "CREATE OR REPLACE FUNCTION $2"
psql -q -x < "$DIR/$1/functions/$2.sql" > /dev/null
if [ $? -eq 0 ]; then
echo -e " - ${GREEN}COMPLETE${NC}"
else
echo -e " - ${RED}FAILED${NC}"
exit 1
fi
}
for schema in admin admin_internal main main_internal
do
if [ -d "$DIR/$schema/functions" ]; then
for function in $DIR/$schema/functions/*.sql
do
makeFunction $schema $(basename "$function" .sql)
done
fi
done
On most of our Linux machines (development and production, Ubuntu 16.04 and 18.04) this script takes 15-20 seconds. Example:
real 0m14.324s
user 0m6.894s
sys 0m1.742s
However, on our Windows development machines (when run using git-bash) it usually takes around three minutes to run the same script. Example:
real 3m0.825s
user 0m3.525s
sys 0m11.943s
(Thinking the issue might be with Bash on Windows, I tried converting the script to PowerShell, only to see the same issue. Thankfully I saw that it wouldn't make a difference while doing partial testing before spending too much time on it.)
It turns out that the problem is in actually making the connection to the PostgreSQL server. For example, using time psql -lqt to time listing all databases on the server (these are example numbers, but dozens of test runs have shown that they are consistently the similar to these):
On Ubuntu:
real 0m0.055s
user 0m0.032s
sys 0m0.020s
On Windows:
real 0m0.852s
user 0m0.000s
sys 0m0.030s
As you can see, it takes 15 times longer on Windows. Extend that out to all the times we are calling psql in the update script, and it's no wonder that it takes 9 times longer to run the full script on Windows than on Linux.
It is well known that Postgres performance will never be as good on Windows as Linux because of the one-process-per-connection model and the lack of fork() on Windows, but that should be a bottleneck in the creation of the connection, not in the execution of the commands. (That the bottleneck is in the connection and not the query execution is evidenced by the fact that a single example command is consistently 15x slower, but the whole script with much larger queries being run is only 9-12x slower.)
Is there a way to make this faster for our Windows users? Is there some way to reuse an existing psql session and pipe additional files into it? Or is my only option to rewrite this in some other language and write my own database communication code that reads the files and pipes them to PostgreSQL myself?
Running the connection through PgBouncer instead of directly to Postgres makes a huge difference.
Using it, my script run on Windows is reduced to around 30 seconds.
It's still not quite as fast as on Linux, but I can live with "only" a 6x improvement.
real 0m33.232s
user 0m2.740s
sys 0m9.785s
We have a script that is executed by httpd as the default ec2-user. However when executed the script does not see any of the environmental variables for that user
the variable is set under user ec2-user
myUseVarHome=/home/ec2-user
myScript.sh
#!/bin/bash
myFolder="${myUseVarHome}/test/www"
USER=$(whoami)
echo "Content-type: text/html"
echo ""
echo "hello $USER"
echo "myFolder=$myFolder"
executing script as ec2-user outputs
hello ec2-user
myFolder=/home/ec2-user/test/www
We then set httpd 2.4 conf
<IfModule unixd_module>
User ec2-user
Group ec2-user
</IfModule>
now call the script with
wget 127.0.0.1/myScript.sh
outputs
hello ec2-user
myFolder=/test/www
The output validates the httpd user is ec2-user, same as manually executing the script, however the env variable ${myUseVarHome} is blank or does not exist.
Is this expected behaviour or do we need to call the env variable another way when executed as httpd user?
bash acts differently whether it is a shell or a normal progamming language (like perl or python).
By designed, those settings in ~/.bash_profile, ~/.bashrc, etc. are for users to set things when bash plays the roll of a shell (login shell, interractive shell). Think about environment you have in a xterm (interractive shell) or in ssh sessions (login shell) or in consoles (login shell).
In other hand, bash is also a powerfull progamming language --think about many scripts for managing services in systemd-- which requires a different style of working. Example, when a developer write a system script or a bash program, he/she will not likely to source user defined ~/.bash_profile automatically. It is a normal program, not a shell. A normal program (including bash programs) would naturally inherrit setting in a current working evironement (shell), but not set them.
If we write a program for cron in bash --it is just happenly it is written in bash; in fact, we can write it in python or perl or any other progamming language-- then, we can have an option to sources bash's ~/.bash_profile (read: setting of user's shell, which happenly to be the same language of your programming language):
[ -f /home/user/.bash_profile ] && . /home/user/.bash_profile
However, what if that particular user do not use bash as his/her shell? He/she may use zsh, 'ksh,fish`, etc. So, that's practice does not really work when writing program for public use.
So, you can source ~/.bash_profile if you think it work. But, here, it is not about whether we are able to source a file, it is about how things should works in the system: the design concept. In short: we should view bash as something having 2 rolls: shell and progamming language. Then everything will be clear, easier to understand.
See: How to change cron shell sh to bash
I know I can run this command to spawn a background process and get the PID:
PID=`$SCRIPT > /dev/null 2>&1 & echo $!`
and to run a command under different user:
su - $USER -c "$COMMAND"
I don't want the script to run as root and I can't quite figure out how to combine the two and get the PID of the spawned process.
Thanks!
I think you want the runuser command. General syntax:
runuser -l userNameHere -c 'command'
I suspect that if you set your $SCRIPT variable to the above (with appropriate changes), your first command will do what you want.
To elaborate on: See the following: - stackoverflow.com/questions/9119885/…
See particularly the following quote from Chris Dodd:
Unfortunately there's no easy way to do this prior to bash version 4, when $BASHPID was
introduced. One thing you can do is to write a tiny program that prints its parent PID:...
If you have bash 4 and BASHPID, see $$ in a script vs $$ in a subshell
I don't have version 4, so I can't provide an example of it's usage.
Or write a tiny C program which execvs it's arguments and make it setuid to USER.
Or even make a setuid shell script (not generally recommended). Hopefully the USER is fixed; if not, get the source for runuser, this is essentially what runuser (not a POSIX command) does.
PID=`su - $USER -c "$SCRIPT > /dev/null 2>&1 & echo $!"`
The problems with the your use of su (above) include:
the $! is being executed in the context of the -c sub-shell of su, not the current shell where PID is,
you're requesting that your SCRIPT be run as a login shell, so you don't even know if USER's shell supports $!,
you have no control over the parent-child process chain that su (and the user's shell) create.
IOW, when you use
PID=`$SCRIPT > /dev/null 2>&1 & echo $!`
there's only one program involved, bash, and two (maybe three?) processes that you pretty much have complete control over. When you throw su into the mix, that changes things much more than is apparent on the surface -- bash and su support similar arguments, right?!?
For obvious reasons, su does mucho magic to protect it and its' children's environment from attacks; it doesn't even like being put in the background....
It's kind of late, but here is a two liner will work, seems to need to be two so that it doesn't wait for the $SCRIPT to complete:
su $USER -c "$SCRIPT 2>&1 & >> $LogOrNull echo $! > /some/writeable/path"
PID="$(cat /some/writeable/path)"
/some/writeable/path will need to be writeable by $USER
And the user running these commands will need to have read access
I have a script which executes a git-pull when I log in. The problem is, if I su to a different user and preserve my environment with an su -lp, the script gets run again and usually gets messed up for various reasons because I'm the wrong user. Is there a way to determine in a shell script whether or not I'm currently SUing? I'm looking for a way that doesn't involve hard coding my username into the script, which is my current solution. I use Bash and ZSH as shells.
You could use the output of the who command with the id command:
WHO=`who am i | sed -e 's/ .*//'`
ID_WHO=`id -u $WHO`
ID=`id -u`
if [[ "$ID" = "$ID_WHO" ]]
then
echo "Not su"
else
echo "Is su"
fi
if test "$(id -u)" = "0";
: # commands executed for root
else
: # commands executed for non root
fi
If you are changing user identities with an suid executable, your real and effective user id will be different. But if use use su (or sudo), they'll both be set to the new user. This means that commands that call getuid() or geteuid() won't be useful.
A better method is to check who owns the terminal the script is being run on. This obviously won't work if the process has detached from it's terminal, but unless the script is being run by a daemon, this is unlikely. Try stat -c %U $(tty). I believe who am i will do the same thing on most Unix-like OSes as well.
You can use "$UID" environment variable.
If its value is ZERO, then the user has SUDOed.. Bcos root as $UID==0
Well.... on linux, if I su to another user the process su is in the new user's process list.
sudo... doesn't leave such pleasant things for you.
I'm using zsh... but I don't think anything in this is shell specific.
if:
%ps | grep " su$"
returns anything, then you're running in an su'd shell.
Note: there is a space before su$ in that to exclude command simply ending in su. Doesn't guard against any custom program/script called su, though.
How do I get the name of the active user via the command line in OS X?
as 'whoami' has been obsoleted, it's probably more forward compatible to use:
id -un
If you'd like to display the full name (instead of the username), add the -F flag:
$ id -F
Andrew Havens
I'm pretty sure the terminal in OS X is just like unix, so the command would be:
whoami
I don't have a mac on me at the moment so someone correct me if I'm wrong.
NOTE - The whoami utility has been obsoleted, and is equivalent to id -un. It will give you the current user
whoami
EDIT
The whoami utility has been obsoleted by the id(1) utility, and is equivalent to id -un. The command id -p is suggested for normal interactive use.
Via here
Checking the owner of /dev/console seems to work well.
stat -f "%Su" /dev/console
There are two ways-
whoami
or
echo $USER
You can also use the logname command from the BSD General Commands Manual under Linux or MacOS to see the username of the user currently logged in, even if the user is performing a sudo operation. This is useful, for instance, when modifying a user's crontab while installing a system-wide package with sudo: crontab -u $(logname)
Per man logname:
LOGNAME(1)
NAME
logname -- display user's login name
If you want to know who's currently logged in to the system:
$ w
15:56:14 up 5 days, 20:58, 6 users, load average: 0.43, 0.53, 0.50
USER TTY LOGIN# IDLE JCPU PCPU WHAT
me pts/2 Fri19 1:03m 0.98s 0.98s -/bin/bash
me pts/3 09:55 6:00m 0.43s 0.43s /bin/bash
me pts/5 15:56 0.00s 0.23s 0.00s w
(This is from a Linux system; the formatting on OS X may be slightly different, but the information should be about the same.)
There may be multiple login sessions; UNIX is designed to be a multi-user system, after all.
The question has not been completely answered, IMHO. I will try to explain: I have a crontab entry that schedules a bash shell command procedure, that in turn does some cleanup of my files; and, when done, sends a notification to me using the OS X notification center (with the command osascript -e 'display notification ...). If someone (e.g. my wife or my daughter) switches the current user of the computer to her, leaving me in the background, the cron script fails when sending the notification.
So, Who is the current user means Has some other people become the effective user leaving me in the background? Do stat -f "%Su" /dev/console returns the current active user name?
The answer is yes; so, now my crontab shell script has been modified in the following way:
...
if [ "$(/usr/bin/stat -f ""%Su"" /dev/console)" = "loreti" ]
then /usr/bin/osascript -e \
'display notification "Cleanup done" sound name "sosumi" with title "myCleanup"'
fi
getting username in MAC terminal is easy...
I generally use whoami in terminal...
For example, in this case, I needed that to install Tomcat Server...
You can also retrieve it from the environment variables, but that is probably not secure, so I would go with Andrew's answer.
printenv USER
If you need to retrieve it from an app, like Node, it's easier to get it from the environment variables, such as
process.env.USER.
Define 'active user'.
If the question is 'who is the logged in user', then 'who am i' or 'whoami' is fine (though they give different answers - 'whoami' reports just a user name; 'who am i' reports on terminal and login time too).
If the question is 'which user ID is the effective ID for the shell', then it is often better to use 'id'. This reports on the real and effective user ID and group ID, and on the supplementary group IDs too. This might matter if the shell is running SUID or SGID.
you can open terminal and write down following command:
id -un
or
whoami
This will return your current login username.