Encrypting Sensitive Data in SaltStack

SaltStack, like any good configuration management system, provides a way to distribute sensitive data (like passwords or API keys) to minion nodes in a secure manner. This is supported by the Pillar system, where this data can be added to a tree-like data structure and are then made available to the relevant minions. While this "distribution" of data is secure, "adding" pillar entries still works (at least by default) by editing YAML-formatted plain text files. This means places like the salt master node and the source code repository containing the state/pillar tree are a weak spot, since there's no at-rest encryption for the included pillars.

One solution is to encrypt the pillars using GPG. This requires GnuPG, and works using a public/private keypair where the secrets are encrypted using the public key, and the salt master decrypts them using the private key.

GPG Renderer

Salt supports using renderers which define how data from a given file should be extracted. By default this is set to yaml, but renderers can also be combined using pipes (|). So something like yaml|gpg means pass the input first to the YAML renderer, take the output, and then feed it to the GPG renderer.

The GPG renderer decrypts GPG ciphers. It works by first generating a public/private keypair, and then using the public key to encrypt secrets. These encrypted values are then added to the pillar tree instead of the plain text secrets. The salt master node can then decrypt these values using the private key. This ensures that the pillar data is also encrypted at rest.

Generating keys

The first step is to generate the keypair that will be used for encrypting/decrypting secrets. The following commands (and the rest of the commands in this article) should be run on the salt master node.

$ sudo mkdir /etc/salt/gpgkeys
$ sudo chmod 0700 /etc/salt/gpgkeys
$ sudo gpg --gen-key --homedir /etc/salt/gpgkeys

Now that the keypair is generated, we can export the public key that will be used to encrypt pillars.

$ sudo gpg --homedir /etc/salt/gpgkeys --export --armor > /path/to/public.key

Placing this public key alongside the state/pillar tree in version control is a good idea, so that other developers on the team can add secrets too.

Adding encrypted pillars

To encrypt stuff, the public key needs to first be imported into the development environment.

$ gpg --import /path/to/public.key

The gpg command can now be used to encrypt values.

$ echo -n "hunter2" | gpg --armor --batch --trust-model always --encrypt -r key-name

This prints out a long and cryptic looking string on the terminal, which is the encrypted secret that should be added to the pillar tree.

#!yaml|gpg

app:
    key: |
        -----BEGIN PGP MESSAGE-----
        Version: GnuPG v1

        hQEMA0JzlBGmNEs6AQgAtuhy4tfowtXBXZTEQF1ZFZSvif7pvStK1SfdynbUmp7A
        DLX9Xs1QeFlsgWmooZIN31f/XR9enUGzkfqjECuys8flvL0zS4FmDnsqUipd4l6E
        PLe0sAYEBZITidMxlru1mSbzn3CSGuUnDmfhPd7AYcGaMxIZHwb3OYgijpuv8Gzs
        fCFzYHP/vlovVFcpMVYCKjztPRng66akWeCI0uu9t8FBxQNWYFMBlzHrNyKSPrEt
        q0vSIkS+lR+te8MKCVO+4KCas7UiKXfJ3pI0faigcRFyzX6FkwEI1xZgV+V9Rbun
        Df2+zEax+YUk2YSy5S4rIb8qB6A0zskxGdfKvF9KB9JCAXoti6zde7iqhbu6/V92
        rupyOWaJpRUXEdqb2ANrR2aQNSv+iA06bjwBMdixfaLWQ7TtyxuWjl9X4fQkwnvK
        FEYW
        =QHlJ
        -----END PGP MESSAGE-----

With some delay (that Salt takes to refresh pillars automatically), requesting the pillar value for app:key should now show the decrypted value.

$ sudo salt '*' pillar.get app:key
test-minion:
    hunter2