Renderer that will decrypt GPG ciphers
Any key in the SLS file can be a GPG cipher, and this renderer will decrypt it before passing it off to Salt. This allows you to safely store secrets in source control, in such a way that only your Salt master can decrypt them and distribute them only to the minions that need them.
The typical use-case would be to use ciphers in your pillar data, and keep a secret key on your master. You can put the public key in source control so that developers can add new secrets quickly and easily.
This renderer requires the gpg binary. No python libraries are required as of the 2015.8.0 release.
When running gpg commands, it is important to run commands as the user that owns
the keys directory. If salt-master runs as user salt, then su salt
before
running any gpg commands.
To avoid compatibility and upgrade problems and to provide a standardized location
for keys, salt uses /etc/salt/gpgkeys
. In order to make the gpg command use
this directory, use gpg --homedir /etc/salt/gpgkeys
with gpg commands or set
the homedir for that user using echo 'homedir /etc/salt/gpgkeys' >> ~/.gnupg
.
To set things up, first generate a keypair. On the master, run the following:
# mkdir -p /etc/salt/gpgkeys
# chmod 0700 /etc/salt/gpgkeys
# gpg --gen-key --homedir /etc/salt/gpgkeys
Do not supply a password for the keypair, and use a name that makes sense for
your application. Be sure to back up the gpgkeys
directory someplace safe!
Note
Unfortunately, there are some scenarios - for example, on virtual machines
which don’t have real hardware - where insufficient entropy causes key
generation to be extremely slow. In these cases, there are usually means of
increasing the system entropy. On virtualised Linux systems, this can often
be achieved by installing the rng-tools
package.
If the keys already exist and need to be imported to the salt master, run the following to import them.
gpg --homedir /etc/salt/gpgkeys --import /path/to/private.key
gpg --homedir /etc/salt/gpgkeys --import /path/to/pubkey.gpg
Note: The default GPG Homedir <gpg-homedir> is ~/.gnupg
and needs to be
set using --homedir
.
In some cases, importing existing keys may not be enough and the trust level of
the key needs to be adjusted. This can be done by editing the key. The key_id
and the actual trust level of the key can be seen by listing the already imported
keys.
gpg --homedir /etc/salt/gpgkeys --list-keys
gpg --homedir /etc/salt/gpgkeys --list-secret-keys
If the trust-level is not ultimate
it needs to be changed by running
gpg --homedir /etc/salt/gpgkeys --edit-key <key_id>
This will open an interactive shell for the management of the GPG encryption key.
Type trust
to be able to set the trust level for the key and then select 5
(I trust ultimately)
. Then quit the shell by typing save
.
In some cases, it's preferable to have gpg keys stored on removable media or
other non-standard locations. This can be done using the gpg_keydir
option
on the salt master. This will also require using a different path to --homedir
,
as mentioned in the GPG Homedir <gpg-homedir> section.
gpg_keydir: <path/to/homedir>
# gpg --homedir /etc/salt/gpgkeys --armor --export <KEY-NAME> > exported_pubkey.gpg
To encrypt secrets, copy the public key to your local machine and run:
$ gpg --import exported_pubkey.gpg
To generate a cipher from a secret:
$ echo -n "supersecret" | gpg --armor --batch --trust-model always --encrypt -r <KEY-name>
To apply the renderer on a file-by-file basis add the following line to the top of any pillar with gpg data in it:
#!yaml|gpg
Now with your renderer configured, you can include your ciphers in your pillar data like so:
#!yaml|gpg
a-secret: |
-----BEGIN PGP MESSAGE-----
Version: GnuPG v1
hQEMAweRHKaPCfNeAQf9GLTN16hCfXAbPwU6BbBK0unOc7i9/etGuVc5CyU9Q6um
QuetdvQVLFO/HkrC4lgeNQdM6D9E8PKonMlgJPyUvC8ggxhj0/IPFEKmrsnv2k6+
cnEfmVexS7o/U1VOVjoyUeliMCJlAz/30RXaME49Cpi6No2+vKD8a4q4nZN1UZcG
RhkhC0S22zNxOXQ38TBkmtJcqxnqT6YWKTUsjVubW3bVC+u2HGqJHu79wmwuN8tz
m4wBkfCAd8Eyo2jEnWQcM4TcXiF01XPL4z4g1/9AAxh+Q4d8RIRP4fbw7ct4nCJv
Gr9v2DTF7HNigIMl4ivMIn9fp+EZurJNiQskLgNbktJGAeEKYkqX5iCuB1b693hJ
FKlwHiJt5yA8X2dDtfk8/Ph1Jx2TwGS+lGjlZaNqp3R1xuAZzXzZMLyZDe5+i3RJ
skqmFTbOiA===Eqsm
-----END PGP MESSAGE-----
New in version 2016.3.0.
Functions like state.highstate
and
state.sls
allow for pillar data to be
passed on the CLI.
salt myminion state.highstate pillar="{'mypillar': 'foo'}"
Starting with the 2016.3.0 release of Salt, it is now possible for this pillar data to be GPG-encrypted, and to use the GPG renderer to decrypt it.
To pass encrypted pillar data on the CLI, the ciphertext must have its newlines
replaced with a literal backslash-n (\n
), as newlines are not supported
within Salt CLI arguments. There are a number of ways to do this:
With awk or Perl:
# awk
ciphertext=`echo -n "supersecret" | gpg --armor --batch --trust-model always --encrypt -r user@domain.com | awk '{printf "%s\\n",$0} END {print ""}'`
# Perl
ciphertext=`echo -n "supersecret" | gpg --armor --batch --trust-model always --encrypt -r user@domain.com | perl -pe 's/\n/\\n/g'`
With Python:
import subprocess
secret, stderr = subprocess.Popen(
['gpg', '--armor', '--batch', '--trust-model', 'always', '--encrypt',
'-r', 'user@domain.com'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate(input='supersecret')
if secret:
print(secret.replace('\n', r'\n'))
else:
raise ValueError('No ciphertext found: {0}'.format(stderr))
ciphertext=`python /path/to/script.py`
The ciphertext can be included in the CLI pillar data like so:
salt myminion state.sls secretstuff pillar_enc=gpg pillar="{secret_pillar: '$ciphertext'}"
The pillar_enc=gpg
argument tells Salt that there is GPG-encrypted pillar
data, so that the CLI pillar data is passed through the GPG renderer, which
will iterate recursively though the CLI pillar dictionary to decrypt any
encrypted values.
If several values need to be encrypted, it may be more convenient to encrypt the entire CLI pillar dictionary. Again, this can be done in several ways:
With awk or Perl:
# awk
ciphertext=`echo -n "{'secret_a': 'CorrectHorseBatteryStaple', 'secret_b': 'GPG is fun!'}" | gpg --armor --batch --trust-model always --encrypt -r user@domain.com | awk '{printf "%s\\n",$0} END {print ""}'`
# Perl
ciphertext=`echo -n "{'secret_a': 'CorrectHorseBatteryStaple', 'secret_b': 'GPG is fun!'}" | gpg --armor --batch --trust-model always --encrypt -r user@domain.com | perl -pe 's/\n/\\n/g'`
With Python:
import subprocess
pillar_data = {'secret_a': 'CorrectHorseBatteryStaple',
'secret_b': 'GPG is fun!'}
secret, stderr = subprocess.Popen(
['gpg', '--armor', '--batch', '--trust-model', 'always', '--encrypt',
'-r', 'user@domain.com'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate(input=repr(pillar_data))
if secret:
print(secret.replace('\n', r'\n'))
else:
raise ValueError('No ciphertext found: {0}'.format(stderr))
ciphertext=`python /path/to/script.py`
With the entire pillar dictionary now encrypted, it can be included in the CLI pillar data like so:
salt myminion state.sls secretstuff pillar_enc=gpg pillar="$ciphertext"
The default behaviour of this renderer is to log a warning if a block could not be decrypted; in other words, it just returns the ciphertext rather than the encrypted secret.
This behaviour can be changed via the gpg_decrypt_must_succeed configuration
option. If set to True, any gpg block that cannot be decrypted raises a
SaltRenderError exception, which registers an error in _errors
during
rendering.
In the Chlorine release, the default behavior will be reversed and an error
message will be added to _errors
by default.
salt.renderers.gpg.
render
(gpg_data, saltenv='base', sls='', argline='', **kwargs)¶Create a gpg object given a gpg_keydir, and then use it to try to decrypt the data to be rendered.