SaltStack: Escaping dollar signs in cmd.run parameters to avoid interpolation

saltstack_logo-thumbnailThe cmd.run state module is a handy way to run commands on a remote host.  However, if your parameter values contain dollar signs ($) they will be interpolated in the target shell, which will lead to unexpected results.

The trick is to escape properly by adding slashes before the dollar sign.

Simple example

Let’s pretend we want to look at the IP addresses in an “/etc/hosts” file using the cmd.run state module.  While locally the command would look like:

cat /etc/hosts | grep -E "^[0-9]" | awk {'print $1'}

If run using the state module, we must escape certain characters.

cmd.run 'myhost' cmd.run "cat /etc/hosts | grep -E \"^[0-9]\" | awk {'print \$1'}"

You must escape the double quotes in the grep because the cmd.run is surrounded by quotes, and then you must escape the dollar sign because otherwise it will be evaluated on the target host and since a variable named “$1” does not exist, it will evaluate to an empty string.

Password hash example

First thing to note is that the purpose of this article is to show how to handle parameter values that contain the dollar sign when using the salt.cmd state module…not best practice for creating users or setting passwords from SaltStack formulas.  The user module handles this well, so I’ve contrived this example because it illustrates the need to escape characters in order to avoid target interpolation.

Let’s pretend we have a local user named “test1”, he will have his password hash in “/etc/shadow“.

The password hash in the shadow file is an aggregate of 3 fields, and these fields are separated by a dollar sign ($).  If you were sitting directly on the console of the target system, you could avoid interpolation by using single quotes around the value.  But when run from the salt state module, this does not work.

So if you wanted salt.cmd to update the password hash for  user named ‘test1’, then first you would create the hash using openssl.

salt 'myserver' cmd.run "openssl passwd -1 -salt 5RVVAd MyP4ssword"

$1$5RVVad$1xWgvWHRN1FE.4YFf09Dy.

Notice this hash has three dollar signs, and if sent without escaping, these values ($1, $5, $1) will end up as empty strings because they will attempt to be evaluated on the target system.  To escape them, we will add three slashes (\\\) before each dollar sign.

Use this escape string when calling usermod to set the password hash as shown below.

salt 'myserver' cmd.run "usermod -p \"\\\$1\\\$5RVVad\\\$1xWgvWHRN1FE.4YFf09Dy.\" test1"

Then verify that the value ended up in the shadow file as intended.

salt 'myserver' cmd.run "cat /etc/shadow | grep test1"

 

REFERENCES

https://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html (man)

https://askubuntu.com/questions/80444/how-to-set-user-passwords-using-passwd-without-a-prompt (openssl and usermod to set password in /etc/shadow)

https://www.shellhacks.com/linux-generate-password-hash/ (generating password hash)

NOTES

explanation of shadow hash field

The password hash is the second field in the shadow entry, and is an aggregation of 3 fields, separated by dollar signs.  The first value is the hash implementation: md5=$1, Blowfish=$2, sha256=$5, sha512=$6.   The second is the salt value, and the third value is the hash which results from using the hashing mechanism on the concatenation of the salt and user password.

built-in user module for creating user and setting password

create-test1-user:
  user.present:
    - name: test1
    - shell: /bin/bash
    - groups:
      - admins
    - password: MyP4ssword
    - hash_password: true