4 min read

Generate PEM key- and truststores With Puppet

March 7, 2019

This post is a follow-up of Generate pkcs12 key- and truststores with Puppet.
In this post we are going to create PEM key- and truststores with Puppet.

PEM files are base64 encoded X.509 certificates. Enclosed between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- multiple PEM files can be concatinated into key- and truststores. And that is exactly what we are going to do using a Puppet manifest.

For the example use case multiple certificate files are required. Using the commands from the Create a certificate authority and sign server certificates without prompting using openssl post the following files must be provided:

  • localhost_cert.pem
  • localhost_key.pem
  • example.com_cert.pem
  • ca_cert.pem

The certificates will be concatinated into the following stores:

  • webservice-keystore.pem
    • localhost - private key entry
  • webservice-truststore.pem
    • ca - trusted cert entry
  • custom-truststore.pem
    • example.com - trusted cert entry
    • ca - trusted cert entry

All the files are processed in /var/tmp/certificates.

The manifest file is defined as followed:

modules/certbox/manifests/init.pp

class certbox (

    String $host = 'localhost',
    String $cert_dir = '/var/tmp/certificates',

    String $server_ca_cert = "${cert_dir}/ca_cert.pem",
    String $server_cn = $host,
    String $server_cert = "${cert_dir}/${host}_cert.pem",
    String $server_key = "${cert_dir}/${host}_key.pem",
    String $server_key_pass = 'password',
    String $server_keystore = "${cert_dir}/webservice-keystore.pem",
    String $server_truststore = "${cert_dir}/webservice-truststore.pem",

    Array $custom_trust_entries = [
      {
        cn => 'example.com',
        cert => 'example.com_cert.pem'
      },
      {
        cn => 'ca',
        cert => 'ca_cert.pem'
      },
    ],
    String $custom_truststore = "${cert_dir}/custom-truststore.pem",

    String $owner = 'root',
    String $group = 'root',
    String $file_read_mode = 'a=,ug+r',

) {

  # component-wide defaulting of the exec path attribute
  Exec {
    path => ['/usr/bin', '/bin']
  }

  # create copy of sever cert, if file state changes notify keystore assemble task
  file { "${server_cert}.tmp":
    ensure => file,
    owner  => $owner,
    group  => $group,
    mode   => $file_read_mode,
    source => $server_cert,
    notify => Exec["certbox - create server keystore"],
  }

  # check if keystore file does not exist and notify creation task
  exec { "certbox - check if server keystore exists":
    onlyif => "test ! -f $server_keystore",
    command => 'echo "File does not exist."',
    notify => Exec["certbox - create server keystore"],
  }

  # create server keystore if temporary file state changed
  exec { "certbox - create server keystore":
    refreshonly => true,
    command     => "cat ${server_cert} > ${server_keystore}; cat ${server_key} >> ${server_keystore}",
  }

  # ensure server keystore file permissions
  file { $server_keystore:
    ensure => file,
    owner  => $owner,
    group  => $group,
    mode   => $file_read_mode,
  }

  file { "${server_ca_cert}.tmp":
    ensure => file,
    owner  => $owner,
    group  => $group,
    mode   => $file_read_mode,
    source => $server_ca_cert,
    notify => Exec["certbox - create server truststore"],
  }

  exec { "certbox - check if server truststore exists":
    onlyif => "test ! -f $server_truststore",
    command => 'echo "File does not exist."',
    notify => Exec["certbox - create server truststore"],
  }

  exec { "certbox - create server truststore":
    refreshonly => true,
    command     => "cat ${server_ca_cert} > ${server_truststore}",
  }

  file { $server_truststore:
    ensure => file,
    owner  => $owner,
    group  => $group,
    mode   => $file_read_mode,
    notify => Exec["certbox - create server truststore"],
  }

  # create copy for each ca truststore entry
  $custom_trust_entries.each | Integer $index, Hash $entry | {

    file { "${cert_dir}/${entry['cert']}.ca_trust.icam.tmp":
      ensure => file,
      owner  => $owner,
      group  => $group,
      mode   => $file_read_mode,
      source => "${cert_dir}/${entry['cert']}",
      notify => [
        Exec["certbox - cleanup ca truststore"],
        Exec["certbox - add pem ${entry['cn']} to ca truststore"]],
    }

    exec { "certbox - check if ca truststore for ${entry['cn']} exists":
      onlyif => "test ! -f $custom_truststore",
      command => 'echo "File does not exist."',
      notify => Exec["certbox - add pem ${entry['cn']} to ca truststore"],
    }
  }

  # reset ca truststore if temporary entry file state changed
  exec { "certbox - cleanup ca truststore":
    refreshonly => true,
    command     => "echo > ${custom_truststore}",
  }

  # add ca truststore entry if temporary entry file state changed
  $custom_trust_entries.each | Integer $index, Hash $entry | {

    exec { "certbox - add pem ${entry['cn']} to ca truststore":
      refreshonly => true,
      command     => "echo >> ${custom_truststore};cat ${cert_dir}/${entry['cert']} >> ${custom_truststore}",
    }
  }

  file { $custom_truststore:
    ensure  => file,
    owner   => $owner,
    group   => $group,
    mode    => $file_read_mode,
  }
}

This manifest checks if the source PEM files have changed or the target stores does not exist. If one of these cases apply the manifests creates the key- or truststore. Finally, it sets the file mode and ownership.

To apply the manifest with Puppet run the following command:

sudo puppet apply --modulepath=modules/ -e "include certbox"

To verify if the files have been assembled correctly use this keytool commands:

sudo keytool -printcert -file /var/tmp/certificates/custom-truststore.pem

Let me know if this post helped you to resolve a particular use case.

Categories:  Software configuration management

Tags:  puppet , deployment , keytool , openssl , certificate

comments powered by Disqus