Most reliable way to identify the current user through a sudo - linux

I have an application that may or may not be run while users are sudo'ed to a shared user account. I would like to reliably identify who the real user is for a sort of "honor-system" ACL. I think there's some way by tracing parent/group/session process ids the way that the pstree command does, but I'm not sure how to do that best or if there are better alternatives.
I tried getlogin() originally. That works if ./myapp is used, but it fails with 'cat input | ./myapp` (because the "controlling terminal" is a pipe owned by the shared account).
I'd rather not trust environment variables, as I don't want my "honor system" to be completely thwarted by a simply unset, when the information is still available elsewhere.
I'd also like to avoid forcing a lookup in the password database, as that is a remote RPC (NIS or LDAP) and I'm pretty sure wtmp already contains the information I need.

For a shell script, you might use this to get the sudo'ing user:
WHO=$(who am i | sed -e 's/ .*//'`)
and extract the id from the login using:
ID_WHO=$(id -u $WHO)
I'll ferret out the C library equivalent later.

sudo sets the environment variables SUDO_USER, SUDO_UID, and SUDO_GID.
You can test this with:
$ sudo env
[sudo] password for shteef:
TERM=xterm
# [...snip...]
SHELL=/bin/bash
LOGNAME=root
USER=root
USERNAME=root
SUDO_COMMAND=/usr/bin/env
SUDO_USER=shteef
SUDO_UID=1000
SUDO_GID=1000
But if your users have shell access on the shared account, then I suppose you cannot blindly trust this either.

How about:
#!/usr/bin/ksh
username=`id | cut -d"=" -f2 | cut -d" " -f1`
if [ $username == "0(root)" ]
then
print "Yes, the user is root"
else
print "Sorry! the user $username, is not a root"
fi

Related

Pass variables out of an interactive session from bash script

Hello People of the world,
I am trying to write a script that will allow user to failover apps between sites in bash.
Our applications are controlled by Pacemaker and I thought I would be able to write a function that would take in the necessary variables and act. Stop on one site, start on another. Once I have ssh'd to the remote machine, I am unable to get the value of the grep/awk command back for the status of the application in PCS.
I am encountering a few issues, and have tried answers from stackoverflow and other sites.
I send the ssh command to /dev/null 2>&1 as banners pop up on screen that unix admin have on the local user and -q does not deal with it - Does this stop anything being returned?
when using awk '{print \\\\\\$4}' in the code, I get a "backslash not last character on line" error
To get round this, I tried result=$(sudo pcs status | grep nds_$resource), however this resulted in a password error on sudo
I have tried >/dev/tty and >$(tty)
I tried to not suppress the ssh (remove /dev/null 2>&1) and put the output in variable at function call, removing the awk from the sudo pcs status entry.
result=$(pcs_call "$site1" "1" "2" "disable" "pmr")
echo $result | grep systemd
This was OK, but when I added | awk '{print \\\$4}' I then got the fourth word in the banner.
Any help would be appreciated as I have been going at this for a few days now.
I have been looking at this answer from Bruno, but unsure how to implement as I have multiple sudo commands.
Below is my strip down of the function code for testing on one machine;
site1=lon
site2=ire
function pcs_call()
{
site=$1
serverA=$2
serverB=$3
activity=$4
resource=$5
ssh -tt ${site}servername0${serverA} <<SSH > /dev/null 2>&1
sudo pcs resource ${activity} proc_${resource}
sleep 10
sudo pcs status | grep proc_$resource | awk '{print \\\$4}' | tee $output
exit
SSH
echo $output
}
echo ====================================================================================
echo Shutting Down PMR in $site1
pcs_call "$site1" "1" "2" "disable" "pmr"
I'd say start by pasting the whole thing into ShellCheck.net and fixing errors until there are no suggestions, but there are some serious issues here shellcheck is not going to be able to handle alone.
> /dev/null says "throw away into the bitbucket any data that is returned. 2>&1 says "Send any useful error reporting on stderr wherever stdout is going". Your initial statement, intended to retrieve information from a remote system, is immediately discarding it. Unless you just want something to occur on the remote system that you don't want to know more about locally, you're wasting your time with anything after that, because you've dumped whatever it had to say.
You only need one backslash in that awk statement to quote the dollar sign on $4.
Unless you have passwordless sudo on the remote system, this is not going to work out for you. I think we need more info on that before we discuss it any deeper.
As long as the ssh call is throwing everything to /dev/null, nothing inside the block of code being passed is going to give you any results on the calling system.
In your code you are using $output, but it looks as if you intend for tee to be setting it? That's not how that works. tee's argument is a filename into which it expects to write a copy of the data, which it also streams to stdout (tee as in a "T"-joint, in plumbing) but it does NOT assign variables.
(As an aside, you aren't even using serverB yet, but you can add that back in when you get past the current issues.)
At the end you echo $output, which is probably empty, so it's basically just echo which won't send anything but a newline, which would just be sent back to the origin server and dumped in /dev/null, so it's all kind of pointless....
Let's clean up
sudo pcs status | grep proc_$resource | awk '{print \\\$4}' | tee $output
and try it a little differently, yes?
First, I'm going to assume you have passwordless sudo, otherwise there's a whole other conversation to work that out.
Second, it's generally an antipattern to use both grep AND awk in a pipeline, as they are both basically regex engines at heart. Choose one. If you can make grep do what you want, it's pretty efficient. If not, awk is super flexible. Please read the documentation pages on the tools you are using when something isn't working. A quick search for "bash man grep" or "awk manual" will quickly give you great resources, and you're going to want them if you're trying to do things this complex.
So, let's look at a rework, making some assumptions...
function pcs_call() {
local site="$1" serverA="$2" activity="$3" resource="$4" # make local and quotes habits you only break on purpose
ssh -qt ${site}servername0${serverA} "
sudo pcs resource ${activity} proc_${resource}; sleep 10; sudo pcs status;
" 2>&1 | awk -v resource="$resource" '$0~"proc_"resource { print $4 }'
}
pcs_call "$site1" 1 disable pmr # should print the desired field
If you want to cath the data in a variable to use later -
var1="$( pcs_call "$site1" 1 disable pmr )"
addendum
Addressing your question - use $(seq 1 10) or just {1..10}.
ssh -qt chis03 '
for i in {1..10}; do sudo pcs resource disable ipa $i; done;
sleep 10; sudo pcs status;
' 2>&1 | awk -v resource=ipa '$0~"proc_"resource { print $2" "$4 }'
It's reporting the awk first, because order of elements in a pipeline is "undefined", but the stdout of the ssh is plugged into the stdin of the awk (and since it was duped to stdout, so is the stderr), so they are running asynchronously/simultaneously.
Yes, since these are using literals, single quotes is simpler and effectively "better". If abstracting with vars, it doesn't change much, but switch back to double quotes.
# assuming my vars (svr, verb, target) preset in the context
ssh -qt $svr "
for i in {1..10}; do sudo pcs resource $verb $target \$i; done;
sleep 10; sudo pcs status;
" 2>&1 | awk -v resource="$target" '$0~"proc_"resource { print $2" "$4 }'
Does that help?

'su' by using 'script' in Docker returns different results compared to the standard environment

I need to request certain commands via su including password in one line.
I found a solution and it is working in a standard environment (Ubuntu) (more about solution here):
{ sleep 1; echo password; } | script -qc 'su -l user -c id' /dev/null | tail -n +2
But I am faced with the problem that this solution is not suitable in a Docker container environment
Script terminates the command without waiting for echo and as a result i get:
su: Authentication failure
Any help is much appreciated.
Passing the password for su via stdin is problematic for various reasons: the biggest one is probably that your password will end up in the history.
You could instead:
Call the entire script as the specific user and thus enter the password manually
Use sudo with the appropriate NOPASSWD sudoers configuration
In your case you are using docker, so you could just set the USER in your Dockerfile

How to get XDG variables with sudo?

I need to retrieve $XDG_CONFIG_HOME, $XDG_RUNTIME_DIR and $XDG_DATA_HOME of all users.
I try with:
sudo -Eiu user printenv | grep XDG_CONFIG_HOME
But I get nothing.
If I log on with the user account, the simple command:
printenv | grep XDG_CONFIG_HOME
Works fine.
How do I?
TLDR, these variables are not available in the sudo case, and some can even not be set at all in the standard login case.
On my linux system (debian 8) some of the XDG variables are defined when the user's session is initialized with the pam module pam_systemd. The variables defined to be initialised at that point are documented in the man page, but include XDG_RUNTIME_DIR.
This module is only loaded in the login cases - i.e. I need to ssh to the system, or login via the console/gui to get the user session to initialize via the pam_systemd module.
This module is not loaded in the su or the sudo cases.
As a result, when you do a sudo to emulate a user this variable is not set.
Other XDG variables are only applicable when the user is actually logged in at the GUI. I don't get these variable set on the GUI that I use, but the docs for XDG variables specify what they are supposed to default to if they're not set. The default value for XDG_CONFIG_HOME is $HOME/.config, and the default value for XDG_DATA_HOME is $HOME/.local/share.
So you should only expect these variables to be set in the interactive login case, and not via the non-interactive case - sudo would be effectively designated as a non-interactive case, even though you can do interactive things with it.
These values are very much templated - e.g. the XDG_RUNTIME_DIR on my system is /var/run/UID, and, per the man page, does not survive after my last logout; so it's not a place to put permanent files.
Unless you, as a user change these values, I would expect the defaults that are specified in the XDG documentation, the KDE compliance pages to be the ones that are used. There are probably others, but I just picked the KDE compliance one as it calls it out in the systems admin guide.
There are several parts to isolating all general users (non-system accounts) on a Linux box. For your purpose, the easiest is probably reading all users into an array, and then looking at the XDG config for each. A quick bash script for this purpose is:
#!/bin/bash
uarray=( $(awk -F':' -v "limit=$(grep "^UID_MIN" /etc/login.defs | sed -e 's/UID_MIN\s*//')" '{ if ( $3 >= limit ) print $1}' /etc/passwd) )
for i in "${uarray[#]}"; do
printf "\nXDG Config for user: %s\n\n" "$i"
sudo -Eiu $i printenv | grep XDG
done
That will print all XDG environment variables for each user with a brief header to identify whose it is. If you would prefer it all in one line:
for i in $(awk -F':' -v "limit=$(grep "^UID_MIN" /etc/login.defs | sed -e 's/UID_MIN\s*//')" '{ if ( $3 >= limit ) print $1}' /etc/passwd); do printf "\nXDG Config for user: %s\n\n" "$i"; sudo -Eiu $i printenv | grep XDG; done
You can limit or change which XDG environment variables are selected by narrowing the argument for grep.

EXISTS=`ssh xyz 'egrep "$username" /etc/passwd | cut -d':' -f1'` command is not working properly

I am writing a script to add users on multiple linux servers. But first i want to check that mentioned username already exists on that particular server or not.
EXISTS=`ssh xyz 'egrep "$username" /etc/passwd | cut -d':' -f1'`
This is the command i am trying. But its not giving me the output that i want..rather its giving me the all username in passwd file.
The variable EXISTS should have the only that username or it should be empty.
On any modern Linux systems, you have getent. So instead of checking like this, check the return code (via $?) of:
getent passwd $username &>/dev/null
(exits with 0 if the user exists, 2 if it doesn't exist; other error codes described in the manpage)
(note: this is not really a Java question, is it?)
EDIT OK, full code...
ssh thehost getent passwd $username;
RC=$?;
# check for $RC here; if 0, the user exists; if not 0 it doesn't.
# Man getent for more details
Note that another solution would be to try and add the user directly; the command will fail if the user already exists, or some other reason; here again, man useradd, and check for possible return codes.

Using the passwd command from within a shell script

I'm writing a shell script to automatically add a new user and update their password. I don't know how to get passwd to read from the shell script instead of interactively prompting me for the new password. My code is below.
adduser $1
passwd $1
$2
$2
from "man 1 passwd":
--stdin
This option is used to indicate that passwd should read the new
password from standard input, which can be a pipe.
So in your case
adduser "$1"
echo "$2" | passwd "$1" --stdin
[Update] a few issues were brought up in the comments:
Your passwd command may not have a --stdin option: use the chpasswd
utility instead, as suggested by ashawley.
If you use a shell other than bash, "echo" might not be a builtin command,
and the shell will call /bin/echo. This is insecure because the password
will show up in the process table and can be seen with tools like ps.
In this case, you should use another scripting language. Here is an example in Perl:
#!/usr/bin/perl -w
open my $pipe, '|chpasswd' or die "can't open pipe: $!";
print {$pipe} "$username:$password";
close $pipe
The only solution works on Ubuntu 12.04:
echo -e "new_password\nnew_password" | (passwd user)
But the second option only works when I change from:
echo "password:name" | chpasswd
To:
echo "user:password" | chpasswd
See explanations in original post: Changing password via a script
Nowadays, you can use this command:
echo "user:pass" | chpasswd
Read the wise words from:
http://mywiki.wooledge.org/BashFAQ/078
I quote:
Nothing you can do in bash can possibly work. passwd(1) does not read from standard input. This is intentional. It is for your protection. Passwords were never intended to be put into programs, or generated by programs. They were intended to be entered only by the fingers of an actual human being, with a functional brain, and never, ever written down anywhere.
Nonetheless, we get hordes of users asking how they can circumvent 35 years of Unix security.
It goes on to explain how you can set your shadow(5) password properly, and shows you the GNU-I-only-care-about-security-if-it-doesn't-make-me-think-too-much-way of abusing passwd(1).
Lastly, if you ARE going to use the silly GNU passwd(1) extension --stdin, do not pass the password putting it on the command line.
echo $mypassword | passwd --stdin # Eternal Sin.
echo "$mypassword" | passwd --stdin # Eternal Sin, but at least you remembered to quote your PE.
passwd --stdin <<< "$mypassword" # A little less insecure, still pretty insecure, though.
passwd --stdin < "passwordfile" # With a password file that was created with a secure `umask(1)`, a little bit secure.
The last is the best you can do with GNU passwd. Though I still wouldn't recommend it.
Putting the password on the command line means anyone with even the remotest hint of access to the box can be monitoring ps or such and steal the password. Even if you think your box is safe; it's something you should really get in the habit of avoiding at all cost (yes, even the cost of doing a bit more trouble getting the job done).
Here-document works if your passwd doesn't support --stdin and you don't want to (or can't) use chpasswd for some reason.
Example:
#!/usr/bin/env bash
username="user"
password="pass"
passwd ${username} << EOD
${password}
${password}
EOD
Tested under Arch Linux. This passwd is an element of shadow-utils and installed from the core/filesystem package, which you usually have by default since the package is required by core/base.
You could use chpasswd
echo $1:$2 | chpasswd
For those who need to 'run as root' remotely through a script logging into a user account in the sudoers file, I found an evil horrible hack, that is no doubt very insecure:
sshpass -p 'userpass' ssh -T -p port user#server << EOSSH
sudo -S su - << RROOT
userpass
echo ""
echo "*** Got Root ***"
echo ""
#[root commands go here]
useradd -m newuser
echo "newuser:newpass" | chpasswd
RROOT
EOSSH
I stumbled upon the same problem and for some reason the --stdin option was not available on the version of passwd I was using (shipped in Ubuntu 14.04).
If any of you happen to experience the same issue, you can work it around as I did, by using the chpasswd command like this:
echo "<user>:<password>" | chpasswd
Tested this on a CentOS VMWare image that I keep around for this sort of thing. Note that you probably want to avoid putting passwords as command-line arguments, because anybody on the entire machine can read them out of 'ps -ef'.
That said, this will work:
user="$1"
password="$2"
adduser $user
echo $password | passwd --stdin $user
This is the definitive answer for a teradata node admin.
Go to your /etc/hosts file and create a list of IP's or node names in a text file.
SMP007-1
SMP007-2
SMP007-3
Put the following script in a file.
#set a password across all nodes
printf "User ID: "
read MYUSERID
printf "New Password: "
read MYPASS
while read -r i; do
echo changing password on "$i"
ssh root#"$i" sudo echo "$MYUSERID":"$MYPASS" | chpasswd
echo password changed on "$i"
done< /usr/bin/setpwd.srvrs
Okay I know I've broken a cardinal security rule with ssh and root
but I'll let you security folks deal with it.
Now put this in your /usr/bin subdir along with your setpwd.srvrs config file.
When you run the command it prompts you one time for the User ID
then one time for the password. Then the script traverses all nodes
in the setpwd.srvrs file and does a passwordless ssh to each node,
then sets the password without any user interaction or secondary
password validation.
For me on Raspbian it works only this way (old password added):
#!/usr/bin/env bash
username="pi"
password="Szevasz123"
new_ps="Szevasz1234"
passwd ${username} << EOD
${password}
${new_ps}
${new_ps}
EOD
Have you looked at the -p option of adduser (which AFAIK is just another name for useradd)? You may also want to look at the -P option of luseradd which takes a plaintext password, but I don't know if luseradd is a standard command (it may be part of SE Linux or perhaps just an oddity of Fedora).
Sometimes it is useful to set a password which nobody knows. This seems to work:
tr -dc A-Za-z0-9 < /dev/urandom | head -c44 | passwd --stdin $user
echo 'yourPassword' | sudo -S yourCommand
if -S doesnt work try with -kS
You can use the expect utility to drive all programs that read from a tty (as opposed to stdin, which is what passwd does). Expect comes with ready to run examples for all sorts of interactive problems, like passwd entry.

Resources