For the past year I’ve been using Salt to manage my company’s users, SSH keys and SSH-based access.
When I started, I had a simple State that had Jinja for-loops, pulled user data from each hosts’ Pillars and then applied the state, creating users, managing their SSH keys, etc etc,
This worked great for us, but now I’d like to add some more complex logic to this,
for example, we have a shared service account that we use to SSH into a host, ie,
john@johnsmac: ssh spaceball@nycweb01mary@marysmac: ssh spaceball@nycweb01
This ‘spaceball’ shared account is used to run shared services, shell scripts, crons, etc and is a very important account. It was vital for us to control this account’s authorized_keys file, and have the ability to add user’s keys to this file. (as well as auditing who has access to what, and also having the ability to lock down a user from accessing a host)
The details of the example that I’m mentioning arent important, because what I want to show is the power of Salt’s renderer system. Because my requirements need complex If/Then statements, complex logic, using the default Jinja render for this state quickly becomes impractical.
Because of Jinja’s syntax, anything more complex than very simple if/then blocks, becomes a nightmare to read and manage. For this reason, I decided to use raw python as a Render.
In the following example, I will create a centralized User YAML file that contains all users’ data, as well as a Python script that controls each user and their SSH access.
Python Renderer
Salt allows you to use any number of renderers, including Jinja, Mako, Cheetah, etc
For this example I’m using a raw Python renderer which will make it easy for me to do complex python logic processing.
Here I will create a centralized User file that contains basic user info like Fullname, UID, group memberships and SSH public keys, here’s a sample file
I will place this into my formula directory under
/salt/formulas/users/files/all_users.yaml
The reason I’m using a YAML file instead putting all this stuff into a Pillar is that there will be lots of user data and processing all users and their keys for each host via Pillar is inefficient. In this example, my User State will just read in this YAML and process the data.
The Engine
Now for the actual engine that will read-in and process all this user data,
This looks complex but its actually simple Python, with some added Salt keywords. Notice the !py shebang at the top, this tells Salt to render it as raw Python.
The main run() function opens our ‘all_users.yaml’ for reading
It also grabs a user pillar from each host, basically on each host we say what users we want to be present, ie
cat /salt/pillar/nycweb01:users:
- dhelmet
- lstar
Once YAML is loaded, it then parses each user.
If the user is both present in the Host pillar and the ‘all_users.yaml’, it will create the user on the host if user is not present, then it will add the SSH keys into the user’s authorized_keys file
For service account “spaceball”, it will add users’ SSH keys depending on the access give for each host.
If Salt is running on nycweb01 host, it will add ‘dhelmet’ and ‘pvespa’ SSH keys to ‘spaceball’ account’s authorized_keys file
if running on nycweb02, it will add only ‘lstar’ and remove anything else that was added. This is total control of each user’s SSH access, as well as auditing.
This shows a relatively simple way to use Python as a Saltstack renderer and have all the benefits of Python’s modular ecosystem inside your Salt logic.