I’m using Password store for a long time and I
love it. It is simple, intuitive and it just works. Recently I started adding
some more formulas into my Saltstack and I started
searching for a way how to store some password that are going to be set via
Salt. It stand to reason to check whether I can use Password Store with Salt
somehow. I found that there is pass
renderer already part of Salt itself. I
checked it out, but in the end, I ended up forking it. Continue reading to
find out how, why and what is the result and what is my final setup.
Password Store workflow
If you don’t know about Password Store, let’s cover the basics first. Password Store stores passwords in directories encrypted using your PGP key. Not just yours. You can specify (per directory) who will be able to decrypt the stored passwords. That is the first cool functionality that seems useful with regards to Salt - I can use my personal secure key to encrypt and decrypt everything, but I can let Salt with his key decode the secrets I want to share. Same way, if there would be more people managing my servers, I could share only subsets of secrets with them.
Other great feature is that Password Store works great together with git
.
That provides an easy way how to share encrypted data across computers. In
particular, I decided to separate my personal passwords and Salts passwords.
Salt has its own git repository and I imported it as a git submodule to my own
git repository with my passwords. This way, I can easily get any password that
Salt knows as well as any of my passwords. But even if you don’t want to play
with submodules, it provides an easy way how to synchronize and version
passwords.
Existing Password Store integration
One cool feature that Salt has is custom renderers. You can write a simple code, that will transform either text or even parsed data into something else. You can combine them, pipe the data through multiple renderers and only the end result is used by Salt.
One of the officially provided renderers is pass. It uses Password Store as a backend. Seems like job done and all I need to do is start using it.
Unfortunately I found some small issues that were limiting its usability.
Actually one issue that has two ugly consequences. pass
renderer tries to
pass every value that you specified in your pillar to the Password Store and
decrypt it. Quite some of those values will not be there so you will get a
bunch of errors in your log. On the other hand, some of them might be there
accidentally. You store there password for one formula using key that makes
sense, but in other one you would need to use the same string as a raw value
and might forget that it is already a path in pass. So it might expand even
something you don’t want expanded. You can’t specify what to try to decrypt and
what not.
Apart from that, providing default values seemed like it would be quite complicated and I would need either support in formula or involve yet another renderer.
My solution - atpass
I forked the pass
renderer and changed it slightly. It ignores any element
except those with values starting with @pass
. Those will be expanded if
possible. After a key word @pass
, it expects the list of possible paths
within Password Store separated by spaces. That means that you can’t have paths
containing spaces in your Password Store. But you can provide multiple paths
and the first one that succeeds is used. If none works, the original value is
used. This allows you to specify the default value and override it in specific
cases. For example like this:
#!jinja|yaml|atpass mysql: server: root_password: "@pass mysql/{{ grains['id'] }}/root mysql/default/root"
As I changed the syntax and the behaviour, I did not send this to the upstream repository, but rather really forked it. If you want to give it a try, you can check out my repo on github. Changes are really minimal (I don’t know Python), but usability is much better in my opinion.