Search code examples
mariadbpuppethieraeyaml

Using MySQL Puppet module and Hiera eyaml for storing encrypted user passwords


I am running the Puppet Server (v8) on a host and configured the Puppet agent (v8) on another host, all running on Rocky 8. I have installed the MySQL module from Puppet Forge.

How do I define an encrypted user password without overwriting the hiera.yaml and common.yaml in the MySQL module directory?

My Puppet folder structure on the server is:

etc/
├─ puppetlabs/
│  ├─ code/
│  │  ├─ environments/
│  │  │  ├─ production/
│  │  │  │  ├─ data/
│  │  │  │  │  ├─ common.yaml
│  │  │  │  ├─ modules/
│  │  │  │  ├─ manifests/
│  │  │  │  │  ├─ site.pp
│  │  │  │  ├─ hiera.yaml
│  │  ├─ modules/
│  │  │  ├─ mysql/
│  │  │  │  ├─ data/
│  │  │  │  │  ├─ common.yaml
│  │  │  │  ├─ manifest/
│  │  │  │  │  ├─ server.pp
│  │  │  │  ├─ hiera.yaml
│  │  │  │  ├─ and more...

I'm looking to encrypt the root password for MySQL using eyaml installed on the Puppet Server. I have run the following to generate the keys and encrypt the password:

eyaml createkeys
eyaml encrypt -l 'my_mysql_password' -s 'mypassword' 

The above generated an encrypted key in the format ENC[PKCS7,...==].

My production environment hiera.yaml is: /etc/puppetlabs/code/environments/production/hiera.yaml

---
version: 5
defaults:
  datadir: data
  data_hash: yaml_data
hierarchy:
  - name: "Per-node data (yaml version)"
    path: "nodes/%{::trusted.certname}.yaml"
  - name: "Other YAML hierarchy levels"
    paths:
      - "common.yaml"
  - name: 'common'
    lookup_key: eyaml_lookup_key
    paths:
      - "common.yaml"
    options:
      pkcs7_private_key: /etc/puppetlabs/puppet/keys/private_key.pkcs7.pem
      pkcs7_public_key:  /etc/puppetlabs/puppet/keys/public_key.pkcs7.pem

My production environment common.yaml is: /etc/puppetlabs/code/environments/production/data/common.yaml

---
mysql::server::root_password: >
  ENC[PKCS7,...==]

My production manifest contains: /etc/puppetlabs/code/environments/production/manifests/site.pp

class { 'mysql::server':
        package_name            => 'mariadb-server',
        root_password           => lookup('mysql::server::root_password'),
        remove_default_accounts => true,
        restart                 => true,
        override_options => {
          mysqld => {
            'max_connections' => '500',
          },
        }
      }

Solution

  • My Puppet folder structure on the server is: [...]

    That's a little unusual. When I install modules with the puppet module command, they ordinarily go into the default environment's module space, but your puppetlabs-mysql went into the global module space. I think that should still be ok, though.

    How do I define an encrypted user password without overwriting the hiera.yaml and common.yaml in the MySQL module directory?

    You are right to avoid modifying the files inside the module itself. They belong to the module. Instead, customize properties of your modules by modifying a data file in your global or environment Hiera hierarchy. For example, /etc/puppetlabs/code/environments/production/data/common.yaml.

    I see that you have in fact done that, and I presume that you ask the question because it's not working for you as you expect. I can't confirm whether you have eyaml itself installed and configured properly, but being able to use the eyaml command as you describe seems to say so.

    The main issue I see is in your production-environment Hiera configuration: you have configured two different hierarchy levels based on the same common.yaml data file. That means that lookups using the default first-found strategy will always be served by the first-listed of those two, which uses the default YAML back-end. In your case, then, you'll get the ciphertext of the password instead of the corresponding decrypted text.

    You have two viable options here:

    1. move the encrypted data to a separate file, and reconfigure Hiera accordingly. Maybe something like this:

      ---
      version: 5
      defaults:
        datadir: data
        data_hash: yaml_data
      hierarchy:
        - name: "Per-node data (yaml version)"
          path: "nodes/%{::trusted.certname}.yaml"
        - name: "Common data"
          paths:
            - "common.yaml"
        - name: 'Encrypted common data'
          lookup_key: eyaml_lookup_key
          paths:
            - "secure.eyaml"
          options:
            pkcs7_private_key: /etc/puppetlabs/puppet/keys/private_key.pkcs7.pem
            pkcs7_public_key:  /etc/puppetlabs/puppet/keys/public_key.pkcs7.pem
      

      That supposes that the eyaml data will go in file /etc/puppetlabs/code/environments/production/data/secure.eyaml, leaving the common.yaml file for less sensitive data.

      OR

    2. Eyaml supports mixing encrypted and non-encrypted data, so you can set up a single, eyaml, level for the common data:

      ---
      version: 5
      defaults:
        datadir: data
        data_hash: yaml_data
      hierarchy:
        - name: "Per-node data (yaml version)"
          path: "nodes/%{::trusted.certname}.yaml"
        - name: 'Common data'
          lookup_key: eyaml_lookup_key
          paths:
            - "common.yaml"
          options:
            pkcs7_private_key: /etc/puppetlabs/puppet/keys/private_key.pkcs7.pem
            pkcs7_public_key:  /etc/puppetlabs/puppet/keys/public_key.pkcs7.pem
      

    Additional notes:

    • You have named your Hiera key correctly for automatic data binding to the root_password property of class mysql::server. You don't then need to perform an explicit lookup() when you declare that class. Just omit the root_password property from the class declaration altogether, and Puppet will fill it in with your Hiera data.

    • I have always held that include-like class declarations (using the include, require, or contain keyword) are to be preferred in most situations over resource-like declarations such as you present in the question. That involves moving all your property customizations into your Hiera data.

    • I don't actually see much point to the Eyaml Hiera back-end because it encrypts your data only where is resides statically on your server. If an adversary can get to that to read it then you already have big trouble, even before you consider that they can probably get to your keys, too. The plain text of your secure data will in any case appear in your nodes' catalogs, on both server and client, and the same plain text may also appear in Puppet logs and reports.

      For the particular parameter you are asking about, you can mitigate at least the appearance in reports and logs by configuring Hiera to convert the decrypted password to data type Sensitive. That still leaves the plaintext password residing in the catalog, however, at least on the client.

      IMO, if it's worth going to any effort to secure a particular datum, then you should go straight to writing and using a deferred function to retrieve it dynamically on the client or integrating with a secret store.