DevOps:Puppet,Docker,and Kubernetes
上QQ阅读APP看书,第一时间看更新

Using GnuPG to encrypt secrets

We often need Puppet to have access to secret information, such as passwords or crypto keys, for it to configure systems properly. But how do you avoid putting such secrets directly into your Puppet code, where they're visible to anyone who has read access to your repository?

It's a common requirement for third-party developers and contractors to be able to make changes via Puppet, but they definitely shouldn't see any confidential information. Similarly, if you're using a distributed Puppet setup like that described in Chapter 2, Puppet Infrastructure, every machine has a copy of the whole repo, including secrets for other machines that it doesn't need and shouldn't have. How can we prevent this?

One answer is to encrypt the secrets using the GnuPG tool, so that any secret information in the Puppet repo is undecipherable (for all practical purposes) without the appropriate key. Then we distribute the key securely to the people or machines that need it.

Getting ready

First you'll need an encryption key, so follow these steps to generate one. If you already have a GnuPG key that you'd like to use, go on to the next section. To complete this section, you will need to install the gpg command:

  1. Use puppet resource to install gpg:
    # puppet resource package gnupg ensure=installed
    
    Tip

    You may need to use gnupg2 as the package name, depending on your target OS.

  2. Run the following command. Answer the prompts as shown, except to substitute your name and e-mail address for mine. When prompted for a passphrase, just hit Enter:
    t@mylaptop ~/puppet $ gpg --gen-key
    gpg (GnuPG) 1.4.18; Copyright (C) 2014 Free Software Foundation, Inc.
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Please select what kind of key you want:
     (1) RSA and RSA (default)
     (2) DSA and Elgamal
     (3) DSA (sign only)
     (4) RSA (sign only)
    Your selection? 1
    RSA keys may be between 1024 and 4096 bits long.
    What keysize do you want? (2048) 2048
    Requested keysize is 2048 bits
    Please specify how long the key should be valid.
     0 = key does not expire
     <n> = key expires in n days
     <n>w = key expires in n weeks
     <n>m = key expires in n months
     <n>y = key expires in n years
    Key is valid for? (0) 0
    Key does not expire at all
    Is this correct? (y/N) y
    You need a user ID to identify your key; the software constructs the user ID
    from the Real Name, Comment and Email Address in this form:
     "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
    
    Real name: Thomas Uphill
    Email address: thomas@narrabilis.com
    Comment: <enter>
    You selected this USER-ID:
     "Thomas Uphill <thomas@narrabilis.com>"
    
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
    You need a Passphrase to protect your secret key.
    

    Hit enter twice here to have an empty passphrase

    You don't want a passphrase - this is probably a *bad* idea!
    I will do it anyway. You can change your passphrase at any time,
    using this program with the option "--edit-key".
    
    gpg: key F1C1EE49 marked as ultimately trusted
    public and secret key created and signed.
    
    gpg: checking the trustdb
    gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
    gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
    pub 2048R/F1C1EE49 2014-10-01
     Key fingerprint = 461A CB4C 397F 06A7 FB82 3BAD 63CF 50D8 F1C1 EE49
    uid Thomas Uphill <thomas@narrabilis.com>
    sub 2048R/E2440023 2014-10-01
    
  3. You may see a message like this if your system is not configured with a source of randomness:
    We need to generate a lot of random bytes. It is a good idea to perform
    some other action (type on the keyboard, move the mouse, utilize the
    disks) during the prime generation; this gives the random number
    generator a better chance to gain enough entropy.
    
  4. In this case, install and start a random number generator daemon such as haveged or rng-tools. Copy the gpg key you just created into the puppet user's account on your Puppet master:
    t@mylaptop ~ $ scp -r .gnupg puppet@puppet.example.com:
    gpg.conf 100% 7680 7.5KB/s 00:00 
    random_seed 100% 600 0.6KB/s 00:00 
    pubring.gpg 100% 1196 1.2KB/s 00:00 
    secring.gpg 100% 2498 2.4KB/s 00:00 
    trustdb.gpg 100% 1280 1.3KB/s 00:00
    

How to do it...

With your encryption key installed on the puppet user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.

  1. Create the following directory:
    t@cookbook:~/puppet$ mkdir -p modules/admin/lib/puppet/parser/functions
    
  2. Create the file modules/admin/lib/puppet/parser/functions/secret.rb with the following contents:
    module Puppet::Parser::Functions
      newfunction(:secret, :type => :rvalue) do |args|
        'gpg --no-tty -d #{args[0]}'
      end
    end
  3. Create the file secret_message with the following contents:
    For a moment, nothing happened.
    Then, after a second or so, nothing continued to happen.
  4. Encrypt this file with the following command (use the e-mail address you supplied when creating the GnuPG key):
    t@mylaptop ~/puppet $ gpg -e -r thomas@narrabilis.com secret_message
    
  5. Move the resulting encrypted file into your Puppet repo:
    t@mylaptop:~/puppet$ mv secret_message.gpg modules/admin/files/
    
  6. Remove the original (plaintext) file:
    t@mylaptop:~/puppet$ rm secret_message
    
  7. Modify your site.pp file as follows:
    node 'cookbook' {
      $message = secret('/etc/puppet/environments/production/ modules/admin/files/secret_message.gpg')
      notify { "The secret message is: ${message}": }
    }
  8. Run Puppet:
    [root@cookbook ~]# puppet agent -t
    Info: Caching catalog for cookbook.example.com
    Info: Applying configuration version '1412145910'
    Notice: The secret message is: For a moment, nothing happened. 
    Then, after a second or so, nothing continued to happen.
    Notice: Finished catalog run in 0.27 seconds
    

How it works...

First, we've created a custom function to allow Puppet to decrypt the secret files using GnuPG:

module Puppet::Parser::Functions
  newfunction(:secret, :type => :rvalue) do |args|
    'gpg --no-tty -d #{args[0]}'
  end
end

The preceding code creates a function named secret that takes a file path as an argument and returns the decrypted text. It doesn't manage encryption keys so you need to ensure that the puppet user has the necessary key installed. You can check this with the following command:

puppet@puppet:~ $ gpg --list-secret-keys
/var/lib/puppet/.gnupg/secring.gpg
----------------------------------
sec 2048R/F1C1EE49 2014-10-01
uid Thomas Uphill <thomas@narrabilis.com>
ssb 2048R/E2440023 2014-10-01

Having set up the secret function and the required key, we now encrypt a message to this key:

tuphill@mylaptop ~/puppet $ gpg -e -r thomas@narrabilis.com secret_message

This creates an encrypted file that can only be read by someone with access to the secret key (or Puppet running on a machine that has the secret key).

We then call the secret function to decrypt this file and get the contents:

$message = secret(' /etc/puppet/environments/production/modules/admin/files/secret_message.gpg')

There's more...

You should use the secret function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.

You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.

For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.

If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.

See also

  • The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
  • The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure