1

I have written a script template in salt, where I can configure in the command to run and it will connect to a number of servers in a list and find the version of the software installed on each one (for a nagios check). The check is configured to connect to 4 Linux agents and a single OSX agent.

I have a check for java, and I was just working on the one for maven.

I am getting some weird behaviour, which I don't understand, I'm hoping someone can explain this to me. Even though the /usr/local/bin shows as being in the path for the user I am connecting with, when I check I cannot run programs that live in /usr/local/bin:

Programs that live in /usr/bin work fine:

$ sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac 'java -version'"
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

$ sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac 'which java'"
/usr/bin/java

But when I try the same on something that lives in /usr/local/bin I get this:

$ sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac 'mvn -version'"
bash: mvn: command not found

$ sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac '/usr/local/bin/mvn -version'"
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00)
Maven home: /usr/local/Cellar/maven/3.3.9/libexec
Java version: 1.8.0_121, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.11.6", arch: "x86_64", family: "mac"

$ sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac 'echo $PATH'"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

Note if I specify the full path it works fine, and if I query path /usr/local/bin is there.

I tried exporting the path in the same command and I get this weirdness:

$ sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac 'export PATH="/usr/local/bin";mvn -version'"
/usr/local/Cellar/maven/3.3.9/libexec/bin/mvn: line 53: uname: command not found
/usr/local/Cellar/maven/3.3.9/libexec/bin/mvn: line 114: dirname: command not found
Error: Could not find or load main class org.codehaus.plexus.classworlds.launcher.Launcher

I don't want to use the full path, as on a linux machine the path is different. I wanted to use the short name so this check can be universal.

Can anyone suggest why this happens and a way to work around it?

Rumbles
  • 1,367
  • 3
  • 16
  • 40
  • `/usr/local/bin` is in *your* path, but not the path of the shell trying to execute `mvn`. – chepner Jan 25 '17 at 14:43
  • Thanks, why does it show up when I execute $PATH on the remote machine then? – Rumbles Jan 25 '17 at 14:46
  • No, you've shown the output from `/usr/local/bin/mvn` running on the remote host. It is indeed there. This is a weird problem.. What are the permissions of the the directory for the user you're connecting as? – Nagios Support Jan 25 '17 at 14:50
  • 3
    In `sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac 'echo $PATH'"`, `$PATH` is in a double quoted string on the local host, so it is expanded before the string is sent to the remote host. The single quotes are literal characters sent to the remote host, where they quote a hard-coded string. – chepner Jan 25 '17 at 14:54
  • ...so, if you want a test that **works**, consider: `sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no bamboomac bash -s" <<<'echo "$PATH"'` -- it's critical that the outer quotes (the ones that are locally syntactic) are single, not double. And passing the script to execute on stdin rather than the argument list means that none of your other (many) intervening shells can execute it. – Charles Duffy Jan 25 '17 at 14:57
  • So that makes sense, the path that I see isn't the remote path... so my test wasn't valid. @CharlesDuffy that didn't work, you're using too many and the wrong kinds of " & ' marks, I tried to fix it but got the same result. – Rumbles Jan 25 '17 at 14:59
  • ...to be clear, there -- `su -c` creates a shell, and `ssh` creates a remote shell (basically, `sh -c "$*"` -- which, yes, *is* inherently buggy), and then you've got your explicit bash on top of that. There are *lots* of shells running around here. – Charles Duffy Jan 25 '17 at 15:00
  • @Rumbles, the only quoting error in my comment was missing closing `"`s. Edited; check again. – Charles Duffy Jan 25 '17 at 15:00
  • @NagiosSupport the dir is owned by agent1:admin and has drwxr-xr-x permissions. If I ssh in as user "backup" I am able to run the command "mvn -version" from my ssh session. – Rumbles Jan 25 '17 at 15:01
  • @CharlesDuffy thanks, that works perfectly now and shows that /usr/local/bin isn't in my path.... now how do I fix it? Because this seems to ignore the mac way of using /etc/paths :/ – Rumbles Jan 25 '17 at 15:02

1 Answers1

1

If you want to pass arbitrary arguments:

#!/bin/bash

# default to running mvn -version; this is how we're getting arbitrary arguments through
(( $# )) || set -- -version

printf -v argv_q '%q ' "$@"
sudo su - backup -c 'ssh -q -o StrictHostKeyChecking=no bamboomac bash -s' <<EOF
PATH=/usr/local/bin:\$PATH
mvn $argv_q
EOF

Notes:

  • No export is needed: Updates to a preexisting environment variable all go to the environment automatically.
  • printf '%q' in bash escapes content in such a manner as to survive a single eval pass.
  • A quoted heredoc, as in <<'EOF', passes content through literally, without modification. An unquoted heredoc, as in <<EOF, performs expansions. If you want to be able to substitute in arbitrary contents, then you want those expansions.
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • This seems to work, I just need to figure out how to make that work in the script I was running it from before (it was run as a python subprocess) – Rumbles Jan 25 '17 at 15:06
  • In Python, you can use `pipes.quote()` instead of instead of bash's `printf '%q'`, and you can pass your script as the stdin of a `Popen` object rather than needing to construct a heredoc at all. – Charles Duffy Jan 25 '17 at 15:07
  • ...if you're having trouble doing those things, you might ask a separate question for which Python is in-scope. – Charles Duffy Jan 25 '17 at 15:08
  • I probably will if I can't make sense of that, thanks! – Rumbles Jan 25 '17 at 15:08
  • I managed to get this working from the start you gave me: response = subprocess.check_output(['sudo su - backup -c "ssh -q -o StrictHostKeyChecking=no %s bash -s" <<\'EOF\'\nPATH=/usr/local/bin:$PATH\nmvn -version|grep -i Apache|awk \'{print $3}\'|tr -d \'\n\'\nEOF' % i], shell=True) – Rumbles Jan 25 '17 at 15:38
  • Make it `% pipes.quote(i)`, and you're set. (Right now, someone could enter a username with a space and a command after it to run that command instead, or put `$(rm -rf $HOME)` in their username, or any manner of other malicious mischief). – Charles Duffy Jan 25 '17 at 15:43
  • ...actually, that might not be enough -- `pipes.quote()` will make your argument safe for *one* level of shell expansion, but there's more than one level going on here. Making the above securely parameterizable is a question unto itself. – Charles Duffy Jan 25 '17 at 15:44
  • 1
    You could at least improve it by ditching `shell=True`: `subprocess.check_output(['sudo', 'su', '-', 'backup', '-c', 'ssh -q -o StrictHostKeyChecking=no %s bash -s' % (pipes.quote(i))], stdin='\n'.join(['PATH=/usr/local/bin/:$PATH', '''mvn -version | awk '/Apache/ {print $3}' '''])` is the first thing that comes to mind. – Charles Duffy Jan 25 '17 at 15:49
  • ...actually, I think the above is likely to be actually safe: There should only be one shell pass, the one invoked by `su`. That said, as an aside, `sudo su` is an unfortunate bit of common idiom, using two separate tools when only one is needed -- you can make `sudo` itself invoke a login shell with the `-i` argument, and then you don't need `su` at all. – Charles Duffy Jan 25 '17 at 16:48
  • So close, but I'm getting "AttributeError: 'str' object has no attribute 'fileno' ", I tried setting the stdout to subprocess.PIPE, but I got "ValueError: stdout argument not allowed, it will be overridden." – Rumbles Jan 25 '17 at 21:48
  • I then tried using Popen and forcing to stdout subprocess.PIPE but I got the same str has no attribute fileno error again – Rumbles Jan 25 '17 at 21:49
  • Right -- that looks like it wants a file object rather than a string, which is not a hard change to make. – Charles Duffy Jan 25 '17 at 21:50
  • actually, http://stackoverflow.com/questions/163542/python-how-do-i-pass-a-string-into-subprocess-popen-using-the-stdin-argument is directly on-point: Just make it `stdin=subprocess.PIPE`, and pass the script to `communicate()`. – Charles Duffy Jan 25 '17 at 21:51
  • Thanks, I tried that, but I got "OSError: [Errno 2] No such file or directory", don't worry though, I just hit a bigger issue, I will create a new question for – Rumbles Jan 26 '17 at 09:07
  • New question: http://stackoverflow.com/questions/41870682/how-to-run-the-bash-command-as-a-system-user-without-giving-that-user-the-right – Rumbles Jan 26 '17 at 09:47