Encrypting some fileds in automatic deployments is a mandatory task nowadays, and sometimes, managing multiple environments requires isolated configuration scopes, this isolation should include the security layer also, more even if some configuration files are delegated to different colleagues.

In this post I'm going to explaing how we've set up our puppet architecture to allow us delegate configuration to different teams in an environment/hostgroup basis in Puppet Open-source with Foreman.


Hiera explanation

In order to allow different layers of configuration based on Foreman's hostgroups, we've created some custom facts to split and make accesible the hostgroup's full path from target nodes.

For example, Foreman hostgroup:  level1/level2/level3/final_level

The default behaviour of Foreman ${::hostgroup} variable will  put the whole string level1/level2/level3/final_level into it.

But, we needed to have more control over the hostgroup information, as we deploy with r10k & puppetfile different configurations from different repositories into the data directory with a architecture similar like this one:

  • team1
    • environment1
      • other_classification...
    • environment2
  • team2

So, accessing to information like team membership or environment was desirable by us for setting up a full hierarchy for all hostgroup path.

Custom Facts

The following Puppet class will put under facts.d on each node some files like:

  • parent_hostgroup First hostgroup value (corresponding with team)
  • last_hostgroup Last parent hostgroup (level3 for our example)
  • hostgroup_N all levels available with its index
class facter::facts() {

  $hostgroups = $::hostgroup.split('/')

  $parent_hostgroup = $hostgroups[0]
  $last_hostgroup = $hostgroups[-1]

  file { 'C:\ProgramData\PuppetLabs\facter\facts.d\hostgroup.txt':
    content => "hostgroup=${::hostgroup}"
  }

  file { 'C:\ProgramData\PuppetLabs\facter\facts.d\parent_hostgroup.txt':
    content => "parent_hostgroup=${parent_hostgroup}"
  }

  file { 'C:\ProgramData\PuppetLabs\facter\facts.d\last_hostgroup.txt':
    content => "last_hostgroup=${last_hostgroup}"
  }

  # Generate dynamic hostgroups facts
  $hostgroups.each |Integer $index, String $value| {
    debug("Setting \$::hostgroup_${index} = ${value}")

    file { "C:\ProgramData\PuppetLabs\facter\facts.d\hostgroup_${index}.txt":
      content => "hostgroup_${index}=${value}"
    }
  }
}

Now, after applying the catalog with this class, we can query these facts also locally , (hostgroup info is only available while contacting puppet with `puppet agent -t` if these facts are not applied)

Dynamic keys for each environment!

Yes! now is the moment for setting up different keys for each environment/hostgroup isolating them from each others.

How can we achieve this behaviour? It's quite simple once we have the custom facts we previously talk about. Just reference these facts inside the hiera.yaml file

Generating and placing certificates

Install the required gem:

gem install hiera-eyaml

And then, generate as many keys as required:

eyaml createkeys

First, we should place private keys inside the server and these keys, should not be accesible for anyone except from the puppet server itself for garanteeing the confidentiality of the sensitive data.

A good place where place the files to is /etc/puppetlabs/puppet/keys/<desired_orded>/*.pem Also, we should ensure anyone can red the file contents, how? setting the following permissions for the puppet user owner -r----— will limit read access only to puppet user in the system.

Editing environment-layer hiera.yaml

Given the following entry in our hiera.yaml file,

- name: "3rdParty Hostgroup overridden configuration"
    paths: 
      - "%{hostgroup}/dbcommon.yaml"
      - "%{hostgroup}/server.yaml"

in order to use encrypted fields on it, we should add some properties to the entry:

- name: "3rdParty Hostgroup overridden configuration"
    lookup_key: eyaml_lookup_key
    paths: 
      - "%{hostgroup}/dbcommon.yaml"
      - "%{hostgroup}/server.yaml"
    options:
      pkcs7_private_key: "/etc/puppetlabs/puppet/keys/%{parent_hostgroup}/private_key.pkcs7.pem"
      pkcs7_public_key: "/etc/puppetlabs/puppet/keys/%{parent_hostgroup}/public_key.pkcs7.pem"

lookup_key entry is mandatory with the value eyaml_lookup_key for ensuring the functionality of hiera-eyaml gem.

options entry holds the pkcs7_private_key and pkcs7_public_key used for encrypting and decrypting obfuscated values. (private key is required).

%{parent_hostgroup} is what makes the magic happen, it uses the custom fact previously created to make dynamically decissions of what keys used for decryption.

Adding support of encryption/decryption to our editor/IDE

Terminal

eyaml encrypt -l <key> -s <plain_value>
eyaml edit <eYAML file>
eyaml decrypt -f <file>

VsCode

brandontosch.hiera-eyaml extension

Configuration:

"hiera-eyaml": {
    "eyamlPath": "eyaml",
    "publicKeyPath": "<path>/public_key.pkcs7.pem",
    "privateKeyPath": "<path>/private_key.pkcs7.pem"
}

Usage:

ctrl + shif + P > eyaml