Search code examples
yamlssl-certificatepuppetlets-encrypt

letsencrypt certonly does not seem to run in yaml


I am looking for some advice / help with an issue I am facing in yaml. But it is also puppet related.

I am using a control-repo for my puppet configuration. I am hosting a site of one of my puppet agent VM's. I can get letsencrypt::certonly to work in puppet i.e my letsencrypt.pp file. But, I would like the certonly configuration to be in my .yaml file for my agent VM. The issue is when I run puppet agent on my agent VM the code all runs without any errors. But my SSL certificate is not populated. This should appear in /etc/letsencrypt/live/. I also check if it exists by using a SSL checker online to see if it had been generated else where It had not. So, from what I can tell this code is not running. Like I said if I have this in my letsencrypt.pp file the SSL cert is generated and all works well.

So, I think I don't have the right code for letsencrypt::certonly for yaml. Any help to correct this for yaml would be really appreciated. My nodes yaml file looks like the following apart from a few redaction's.

---
classes:
   - roles::www

# UFW Rules
ufw::rules:
  'Allow HTTP':
    action: allow
    to_ports_app: 80
    proto: tcp
  'Allow HTTPS':
    action: allow
    to_ports_app: 443
    proto: tcp



# nginx rules
nginx::nginx_servers:
  'www.test.domain.com': 
    server_name: ['test.domain.com']
    ipv6_enable: true
    ssl: true
    http2: 'on'
    ipv6_listen_options: ''
    ssl_redirect: true
    ssl_cert: '/etc/letsencrypt/live/test.domain.com/cert.pem' 
    ssl_key: '/etc/letsencrypt/live/test.domain.com/privkey.pem' 
    www_root: '/www-data/www.test.domain.com/' 
    server_cfg_ssl_append:
    ssl_dhparam: '/usr/lib/python3/dist-packages/certbot/ssl-dhparams.pem'

# letsencryot certonly
letsencrypt::certonly:
    'test.domain.com':
      cert_name: 'test.domain.com'
      domains:
        - 'test.domain.com'
        - '*.test.domain.com'
      plugin: dns-cloudflare
      manage_cron: false
      require:
        classes:
          - profiles::nginx
    
# letsencrypt
letsencrypt::email: '[email protected]'
letsencrypt::plugin::dns_cloudflare::email: '[email protected]'

It is only the letsencrypt::certonly section of this file that does not seem to be correct / working.

I am using the puppet-letsencrypt module from the Puppet Forge, version 10.0.0, like so:

class profiles::letsencrypt {

  package { 'cron':
    ensure => installed,
  }

  class { 'letsencrypt':
    config  => {
      email  => '[email protected]',
      server => 'https://acme-v02.api.letsencrypt.org/directory',
    },
    require => Package['cron'],
  }

  file { '/etc/letsencrypt/options-ssl-nginx.conf':
    ensure => file,
  }

  file { '/etc/letsencrypt/ssl-dhparams.pem':
    ensure => file,
  }
  include 'letsencrypt'
  include 'letsencrypt::plugin::dns_cloudflare'
}

Solution

  • You seem to have an incorrect expectation. In the module you are using, letsencrypt::certonly is a resource type. Class parameters are loaded automatically from external data, but resources and classes themselves need to be declared in your manifest set to be included in your nodes' catalogs. You cannot (directly) declare resources via your external data.

    But there are many ways that you can use external data to specify which resources your classes should declare, with which properties. One of them would go like this:

    • give your profile class a parameter with which to associate the data for your letsencrypt::certonly resources. You can rely on your external data being automatically bound to that parameter, supposing only that you use appropriately matched names.

    • Based on a data format very similar to what you're already using (you get to choose, because it is you who is consuming this data), iterate over the property sets for the various certonly resources to declare each one.

    For example:

    class profiles::letsencrypt (
        Hash[String, Hash] $certonlies = {},
    ) {
        # ...
        $certonlies.each() |$t, $params| {
            # declare the resource:
            letsencrypt::certonly { $t:
                * => $params;
            }
        }
    }
    

    The corresponding data might look like this:

    # ...
    profiles::letsencrypt::certonlies:
        'test.domain.com':
          cert_name: 'test.domain.com'
          domains:
            - 'test.domain.com'
            - '*.test.domain.com'
          manage_cron: false
          plugin: 'dns-cloudflare'
          require: 'Class[profiles::nginx]'
    

    Additional notes:

    • I'm not a fan of putting relationship metaparameters into external data, and historically the support for that has been a little fiddly.

    • I'm also not a fan of having application classes and resources such as letsencrypt::certonly declare relationships with profiles. As a matter of best practices with the roles & profiles pattern, profiles should have explicit ordering relationships only with other profiles. Those can be declared either by the profiles themselves or by the role that engages them.

    • If you set up relationships involving classes, especially high-level classes such as profiles, you need to pay attention to containment. It's unclear to me whether you have now or likely would have containment issues, however, so just take this as a heads-up.

    • Your profile already having declared class letsencrypt once (via a resource-like class declaration), it serves no additional purpose for it to declare that class again via an include statement.