Probably the sudoers
file on the remote side specifies the exact command you can run and it's su - serviceAccount
. One or more additional options (like -c
) will make the command not qualify. Any solution should use the exact su - serviceAccount
command.
There is a way to do something about it, but it's not perfect. Start by running this on the server:
echo whoami | sudo su - serviceAccount
(sudo
without -S
will use the terminal to ask for your password (if needed) despite the redirection).
To make the approach more general, create a script on the server:
#!/bin/sh
printf '%s ' "@" | sudo su - serviceAccount
Save it as su-ser
, and make it executable. Now you can test these:
./su-ser whoami
./su-ser 'whoami; whoami'
./su-ser ls -l
./su-ser 'ls -l'
Note these quotes don't propagate to the shell spawned by su
. Each time printf
builds a command from arguments that the script gets, this is passed as a string and then parsed by the final shell, with word splitting, globbing and such. This means all these
./su-ser echo 1 2 3
./su-ser echo "1 2 3"
./su-ser "echo 1 2 3"
will print 1 2 3
. To pass a quoted string you need to quote quotes, e.g.:
./su-ser 'echo "1 2 3"'
The next step is to trigger this remote script from your local computer:
ssh -t tlous@server.example.com '"/path/to/su-ser" whoami'
You need -t
in case sudo
asks for the password. From the ssh
command in your question body I can tell you probably understand this.
Note ssh
also builds a command string. The above quoting would work even if /path/to/su-ser
contained spaces. On the other hand, if the path is without spaces (and without characters like ;
, *
), a totally unquoted ssh … /path/to/su-ser whoami
will work as well.
Proper quoting is not a trivial task at this point. Let's analyze what happens to the command.
- At first your local shell parses the string. It uses the most outer quotes (if any) to parse it right.
ssh
gets its arguments as an array. The quotes used up by the shell don't get this far.
ssh
decides which arguments should be passed to the remote side and builds a string (in similar manner to printf
in our remote script).
- This string is then parsed by the remote shell (the one spawned by
sshd
on the server). The remote shell uses the (now) most outer quotes (if any) to parse it right.
su-ser
gets its arguments as an array. The quotes used up by the remote shell don't get this far.
su-ser
builds a string.
- This string is then parsed by yet another shell (spawned by
su
on the server). The shell uses the (now) most outer quotes (if any) to parse it right.
- The command(s) you passed gets its arguments as an array. The quotes used up by the last shell don't get this far.
This means sometimes you need three levels of quotes to get the right result. An uncomplicated local command like
echo "1 2 3"
becomes
ssh -t tlous@server.example.com "'/path/to/su-ser' 'echo \"1 2 3\"'"
where the unescaped double-quotes get stripped by the local shell, the single-quotes get stripped by the remote shell; finally the escaped double-quotes (that in fact become unescaped as soon as the local shell strips the unescaped ones) tell the shell spawned by su
that the string with multiple spaces is a single argument to echo
. This way the output is
1 2 3
Another problem is the remote script passes commands to stdin of the final shell, so you can't easily use its stdin for another purpose. E.g. cat; whoami
(try it) should print back whatever lines you type (terminate it with Ctrl+D), then the output of whoami
. However if you run this on the server:
./su-ser 'cat; whoami'
cat
will terminate immediately. This should work:
./su-ser 'cat /dev/tty; whoami'
(Again, use Ctrl+D to terminate cat
). Now compare the behavior of this:
./su-ser '
whoami
whoami
whoami
'
to this:
./su-ser '
whoami
cat
whoami
'
The same stream feeds cat
and the shell; it's not a good thing. Establishing separate channels for actual commands and the real stdin may not be easy because sudo
by default closes additional file descriptors and sets a new, minimal environment. I guess a contraption using temporary files/fifos would work, but this is so hairy I won't even try.
So the script may or may not be enough for you; it depends on commands you need to run. Now you know a couple of its limitations. Good luck.
Exactly what I needed. Thanks for the thorough explanation! – Tom Lous – 2019-04-16T14:46:57.130