published: 16th of June 2018
Salt SSH allows you to execute commands and apply state to minions without having to install a salt-minion . The only requirement is for the minion to have python installed unless using the -r option to execute raw commands.
For reference the following software will be used in this post.
Create and change into a working directory named salt-ssh .
mkdir /home/bradmin/salt-ssh && cd /home/bradmin/salt-ssh
I will use pipenv to manage a virtual environment and the installation of salt-ssh . If you are not using pipenv you can use pip with a virtual environment to install salt-ssh .
pipenv install salt-ssh
Activate the virtual environment.
pipenv shell
By default Salt expects its configuration files to be located in the /etc/salt directory. I am running Salt SSH as a non-root user so there are a number of directories that need to be created which are writable for my user.
mkdir -p {config,salt/{files,templates,states,pillar,formulas,pki/master,logs}}
# Directory Structure
/home/bradmin/salt-ssh
├── config
└── salt
├── files
├── formulas
├── logs
├── pillar
├── pki
│ └── master
├── states
└── templates
The master config file has the same declarations that you would define when using Salt in master mode. Create a master config file with the following contents that points Salt SSH to the location of the previously created directories.
# salt-ssh/config/master
file_roots:
base:
- "/home/bradmin/salt-ssh/salt"
cachedir: "/home/bradmin/salt-ssh/salt/cache"
pki_dir: "/home/bradmin/salt-ssh/salt/pki/master"
The roster file is used to define remote minions and their connection parameters. I am using Vagrant for this lab so I will reuse the Vagrant user credentials. Create a roster file with the following contents.
# salt-ssh/config/roster
minion:
host: "192.168.121.117"
user: "vagrant"
priv: "~/.vagrant.d/insecure_private_key"
The Saltfile allows you to set command line configuration option in a file instead of declaring them at runtime. Create a Saltfile with the following contents.
# salt-ssh/Saltfile
salt-ssh:
config_dir: "/home/bradmin/salt-ssh/salt/config"
roster_file: "/home/bradmin/salt-ssh/salt/config/roster"
ssh_log_file: "/home/bradmin/salt-ssh/salt/logs/salt-ssh-log.txt"
log_file: "/home/bradmin/salt-ssh/salt/logs/salt-log.txt"
For reference the final directory structure should look like the following.
# Directory Structure
/home/bradmin/salt-ssh
├── config
│ ├── master
│ └── roster
├── salt
│ ├── cache
│ ├── files
│ ├── formulas
│ ├── logs
│ ├── pillar
│ ├── pki
│ │ └── master
│ ├── state
│ └── templates
└── Saltfile
With the base configuration done, lest run a few tests. Salt SSH is controlled using the salt-ssh command. Aside from that you can use the same command line options that are available with the regular salt command.
salt-ssh '*' test.ping
# output
minion:
True
If you successfully connected lets try install a package.
salt-ssh '*' pkg.install cowsay --sudo
# output
minion:
----------
cowsay:
----------
new:
3.03+dfsg2-3
old:
cowsay-off:
----------
new:
3.03+dfsg2-3
old:
Alright as a final test, lets execute a command on the minion.
salt-ssh '*' cmd.run 'cowsay im a salty moo cow'
# output
minion:
____________________
< im a salty moo cow >
--------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Most of the issues I ran into related to running Salt as a non-root users. You can run salt-ssh with the -l debug flag to get a more detailed look at the execution and errors if any.
salt-ssh '*' cmd.run 'df -h' -l debug
# output
[INFO ] Loading Saltfile from '/home/bradmin/salt-ssh/Saltfile'
[DEBUG ] Reading configuration from /home/bradmin/salt-ssh/Saltfile
[DEBUG ] Reading configuration from /home/bradmin/salt-ssh/config/master
[DEBUG ] Configuration file path: /home/bradmin/salt-ssh/config/master
[WARNING ] Insecure logging configuration detected! Sensitive data may be logged.
[DEBUG ] LazyLoaded flat.targets
[DEBUG ] LazyLoaded jinja.render
[DEBUG ] LazyLoaded yaml.render
[DEBUG ] compile template: ./config/roster
[DEBUG ] Jinja search path: ['/home/bradmin/salt-ssh/salt/cache/files/base']
[DEBUG ] LazyLoaded roots.envs
[DEBUG ] Could not LazyLoad roots.init: 'roots.init' is not available.
[DEBUG ] Updating roots fileserver cache
[PROFILE ] Time (in seconds) to render './config/roster' using 'jinja' renderer: 0.04327249526977539
[DEBUG ] Rendered data from file: ./config/roster:
minion:
host: "192.168.121.117"
user: "vagrant"
priv: "~/.vagrant.d/insecure_private_key"
[DEBUG ] Results of YAML rendering:
OrderedDict([('test', OrderedDict([('host', '192.168.121.117'), ('user', 'vagrant'), ('priv', '~/.vagrant.d/insecure_private_key')]))])
[PROFILE ] Time (in seconds) to render './config/roster' using 'yaml' renderer: 0.0015110969543457031
[DEBUG ] Matched minions: {'test': {'host': '192.168.121.117', 'user': 'vagrant', 'priv': '~/.vagrant.d/insecure_private_key'}}
[DEBUG ] LazyLoaded roots.envs
[DEBUG ] Could not LazyLoad roots.init: 'roots.init' is not available.
[DEBUG ] Updating roots fileserver cache
[DEBUG ] LazyLoaded local_cache.prep_jid
[DEBUG ] Adding minions for job 20180616115608058884: ['minion']
[DEBUG ] Could not LazyLoad cmd.run: 'cmd.run' is not available.
[DEBUG ] Performing shimmed, blocking command as follows:
cmd.run df -h
[DEBUG ] Executed SHIM command. Command logged to TRACE
[DEBUG ] Child Forked! PID: 27215 STDOUT_FD: 10 STDERR_FD: 12
[DEBUG ] VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE
[DEBUG ] RETCODE 192.168.121.117: 0
[DEBUG ] LazyLoaded nested.output
minion:
Filesystem Size Used Avail Use% Mounted on
udev 2.0G 0 2.0G 0% /dev
tmpfs 396M 5.4M 391M 2% /run
/dev/vda1 9.2G 1.4G 7.3G 17% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
tmpfs 396M 0 396M 0% /run/user/1000
Salt SSH is a nice alternative for minions where it is not possible and/or convenient to install a salt-minion . Common uses for running Salt SSH include managing IOT devices and bootstrapping minions with Salt to manage them in master mode.