Forge Home

acme

Centralized SSL certificate management using Let's Encrypt and the lightweight acme.sh

649 downloads

184 latest version

4.7 quality score

We run a couple of automated
scans to help you access a
module's quality. Each module is
given a score based on how well
the author has formatted their
code and documentation and
modules are also checked for
malware using VirusTotal.

Please note, the information below
is for guidance only and neither of
these methods should be considered
an endorsement by Puppet.

Version information

  • 5.0.0 (latest)
  • 4.1.0
  • 4.0.1
released Apr 17th 2024
This version is compatible with:
  • Puppet Enterprise 2023.8.x, 2023.7.x, 2023.6.x, 2023.5.x, 2023.4.x, 2023.3.x, 2023.2.x, 2023.1.x, 2023.0.x, 2021.7.x, 2021.6.x, 2021.5.x, 2021.4.x, 2021.3.x, 2021.2.x, 2021.1.x, 2021.0.x
  • Puppet >= 7.0.0 < 9.0.0
  • , , , ,

Start using this module

  • r10k or Code Manager
  • Bolt
  • Manual installation
  • Direct download

Add this module to your Puppetfile:

mod 'markt-acme', '5.0.0'
Learn more about managing modules with a Puppetfile

Add this module to your Bolt project:

bolt module add markt-acme
Learn more about using this module with an existing project

Manually install this module globally with Puppet module tool:

puppet module install markt-acme --version 5.0.0

Direct download is not typically how you would use a Puppet module to manage your infrastructure, but you may want to download the module in order to inspect the code.

Download

Documentation

markt/acme — version 5.0.0 Apr 17th 2024

puppet-acme

Build Status Puppet Forge Puppet Forge - downloads License

Table of Contents

  1. Overview
  2. Requirements
  3. Workflow
  4. Setup
  5. Usage
  6. Examples
  7. Reference
  8. Limitations
  9. Development
  10. Fork
  11. License

Overview

Centralized SSL certificate management using Let's Encrypt™ or other ACME CA's. Keep your private keys safe on the host they belong to and let your Puppet Server sign the CSRs and distribute the certificates.

Requirements

Furthermore it is highly recommended to use hiera-eyaml to protect sensitive information (such as DNS API secrets).

Workflow

For every configured certificate, this module creates a private key and CSR, transfers the CSR to your Puppet Server where it is signed using the popular and lightweight acmesh-official/acme.sh.

Signed certificates are shipped back to the originating host. The private key is never exposed.

You just need to specify the required challenge configuration on your Puppet Server. All DNS-01 hooks that are supported by acme.sh will work immediately.

Setup

Configure your Puppet Server

The whole idea is centralized certificate management, thus you have to add some configuration on your Puppet Server.

First configure the ACME accounts that are available to issue certificates:

    Class { 'acme':
      accounts => ['certmaster@example.com', 'ssl@example.com']
      ...
    }

Next add configuration for the challenge types you want to use, we call each configuration a "profile":

    Class { 'acme':
      accounts => ['certmaster@example.com', 'ssl@example.com'],
      profiles => {
        nsupdate_example => {
          challengetype => 'dns-01',
          hook          => 'nsupdate',
          env           => {
            'NSUPDATE_SERVER' => 'bind.example.com'
          },
          options       => {
            dnssleep      => 15,
            nsupdate_id   => 'example-key',
            nsupdate_type => 'hmac-md5',
            nsupdate_key  => 'abcdefg1234567890',
          }
        },
        route53_example  => {
          challengetype => 'dns-01',
          hook          => 'aws',
          env           => {
            'AWS_ACCESS_KEY_ID'     => 'foobar',
            'AWS_SECRET_ACCESS_KEY' => 'secret',
          },
          options       => {
            dnssleep => 15,
          }
        }
      }
    }

In this example we create two "profiles": One is utilizing the "nsupdate" hook to communicate with a BIND DNS server and the other one uses the "aws" hook to communicate with Amazon Route53.

Note that the hook parameter must exactly match the name of the hook that is used by acmesh-official/acme.sh. Some DNS hooks require environment variables that contain usernames or API tokens, simply add them to the env parameter.

All CSRs are collected and signed on your Puppet Server via PuppetDB, and the resulting certificates and CA chain files are shipped back to the originating host via PuppetDB.

Usage

Request a certificate

On the Puppet node where you need the certificate(s):

    class { 'acme':
      certificates => {
        # issue a test certificate
        test.example.com => {
          use_profile => 'route53_example',
          use_account => 'ssl@example.com',
          ca          => 'letsencrypt_test',
        }
        # a cert for the current FQDN is nice too
        ${facts['networking']['fqdn']} => {
          use_profile => 'nsupdate_example',
          use_account => 'certmaster@example.com',
          ca          => 'letsencrypt',
        },
      }
    }

Note: The use_profile and use_account parameters must match the profiles and accounts that you've previously configured on your Puppet Server. Otherwise the module will refuse to issue the certificate.

The private key and CSR will be generated on your node and the CSR is shipped to your Puppet Server for signing. The certificate is put on your node as soon as it was signed on your Puppet Server.

Instead of specifying the domains as parameter to the acme class, it is also possible to use the acme::certificate defined type directly:

    acme::certificate { 'test.example.com':
      use_profile => 'route53_example',
      use_account => 'ssl@example.com',
      ca          => 'letsencrypt_test',
    }

Using other ACME CA's

This module uses the Let's Encrypt ACME CA by default. The default ACME CA can be changed on the Puppet Server:

    Class { 'acme':
      default_ca => 'zerossl',
      ca_whitelist => ['letsencrypt', 'letsencrypt_test', 'zerossl'],
      ...
    }

Note that other CA's must always be added to the $ca_whitelist, otherwise issueing certificates will fail for this CA. By default only 'letsencrypt' and 'letsencrypt_test' are whitelisted.

Besides that a different CA can also be specified for individual certificates:

    class { 'acme':
      certificates => {
        test.example.com => {
          use_profile => 'route53_example',
          use_account => 'ssl@example.com',
          ca          => 'zerossl',
        }
      }
    }

SAN certificates

Requesting SAN certificates is easy too. To do so add a space separated list of domain names to the certificates hash. The first domain name in each list is used as the base domain for the request. For example:

    class { 'acme':
      certificates => {
        'test.example.com foo.example.com bar.example.com' => {
          use_profile => 'route53_example',
          use_account => 'ssl@example.com',
          ca          => 'letsencrypt_test',
        }
    }

Or use the defined type directly:

    acme::certificate { 'test.example.com foo.example.com bar.example.com':
      use_profile => 'route53_example',
      use_account => 'ssl@example.com',
      ca          => 'letsencrypt_test',
    }

In both examples "test.example.com" will be used as base domain for the CSR.

Alternatively, you can specify the domains explicitly:

    class { 'acme':
      certificates => {
        'test.example.com' => {
          domain      => 'test.example.com foo.example.com bar.example.com',
          use_profile => 'route53_example',
          use_account => 'ssl@example.com',
          ca          => 'letsencrypt_test',
        }
    }

or as a list of domains:

    class { 'acme':
      certificates => {
        'test.example.com' => {
          domain      => ['test.example.com', 'foo.example.com', 'bar.example.com'],
          use_profile => 'route53_example',
          use_account => 'ssl@example.com',
          ca          => 'letsencrypt_test',
        }
    }

It is recommended to use the first domain as name for the certificate resource, to increase compatibility with the acme.sh script. However, it is not a strict requirement, and is not possible when multiple certificates are required for the same base name (see below).

Multiple certificates for one base domain

Sometimes you need to issue multiple certificates for the same base domain. This can happen either on a single node (for example one certificate with ocsp_must_staple flag set, and one without it), or on multiple nodes (for example two failover nodes serving the same domain).

You can use the resource name to specify a unique identifier, and the domain parameter to explicitly list the domain(s):

    class { 'acme':
      certificates => {
        'mail.example.com (webserver)' => {
          domain      => 'mail.example.com',
          use_profile => 'route53_example',
          use_account => 'ssl@example.com',
          ca          => 'letsencrypt_test',
        },
        'mail.example.com (mailserver)' => {
          domain           => 'mail.example.com',
          use_profile      => 'route53_example',
          use_account      => 'ssl@example.com',
          ca               => 'letsencrypt_test',
          ocsp_must_staple => true,
        }
      }
   }

Please note that this functionality relies on an undocumented feature of acme.sh.

DNS alias mode

In order to use DNS alias mode, specify the domain name either in the challenge_alias or domain_alias parameter of your profile:

    Class { 'acme':
      accounts => ['certmaster@example.com', 'ssl@example.com'],
      profiles => {
        route53_example  => {
          challengetype   => 'dns-01',
          challenge_alias => 'alias-example.com',
          hook            => 'aws',
          env             => {
            AWS_ACCESS_KEY_ID     => 'foobar',
            AWS_SECRET_ACCESS_KEY => 'secret',
          },
          options         => {
            challenge_alias => 'alias-example.com',
            dnssleep        => 15,
          }
        }
      }
    }

Testing and Debugging

For testing purposes you should use a test CA, such as letsencrypt_test. Otherwise you will hit rate limits pretty soon. It is possible to set the default CA on your Puppet Server by using the default_ca parameter:

    class { 'acme' :
      default_ca => 'letsencrypt_test'
    }

Or you can use this parameter directly when configuring certificates on your Puppet nodes, as shown in the previous examples.

Note that certificates generated by a test CA cannot be validated and as a result will generate security warnings.

Updating acme.sh

This module automatically updates acme.sh to the latest version, which may not always be desirable. It is possible to change the $acme_revision parameter to install a specific version of acme.sh:

    class { 'acme' :
      acme_revision => '9293bcfb1cd5a56c6cede3f5f46af8529ee99624'
    }

The revision should be taken from the official acme.sh repository. In order to revert to the latest version, the value should be set to 'master'.

Examples

Apache

Using acme in combination with puppetlabs-apache:

    # request a certificate from acme
    acme::certificate { $facts['networking']['fqdn']:
      use_profile => 'nsupdate_example',
      use_account => 'certmaster@example.com',
      ca          => 'letsencrypt',
      # restart apache when the certificate is signed (or renewed)
      notify      => Class['apache::service'],
    }

    # get configuration from acme
    include acme
    $base_dir = $acme::base_dir
    $crt_dir  = $acme::crt_dir
    $key_dir  = $acme::key_dir

    # where acme stores our certificate and key files
    $my_key = "${key_dir}/${facts['networking']['fqdn']}/private.key"
    $my_cert = "${crt_dir}/${facts['networking']['fqdn']}/cert.pem"
    $my_chain = "${crt_dir}/${facts['networking']['fqdn']}/chain.pem"

    # configure apache
    include apache
    apache::vhost { $facts['networking']['fqdn']:
      port     => '443',
      docroot  => '/var/www/example',
      ssl      => true,
      ssl_cert => "/etc/ssl/${facts['networking']['fqdn']}.crt",
      ssl_ca   => "/etc/ssl/${facts['networking']['fqdn']}.ca",
      ssl_key  => "/etc/ssl/${facts['networking']['fqdn']fqdn}.key",
    }

    # copy certificate files
    file { "/etc/ssl/${facts['networking']['fqdn']}.crt":
      ensure    => file,
      owner     => 'root',
      group     => 'root',
      mode      => '0644',
      source    => $my_cert,
      subscribe => Acme::Certificate[$facts['networking']['fqdn']],
      notify    => Class['apache::service'],
    }
    file { "/etc/ssl/${facts['networking']['fqdn']}.key":
      ensure    => file,
      owner     => 'root',
      group     => 'root',
      mode      => '0640',
      source    => $my_key,
      subscribe => Acme::Certificate[$facts['networking']['fqdn']],
      notify    => Class['apache::service'],
    }
    file { "/etc/ssl/${facts['networking']['fqdn']}.ca":
      ensure    => file,
      owner     => 'root',
      group     => 'root',
      mode      => '0644',
      source    => $my_chain,
      subscribe => Acme::Certificate[$facts['networking']['fqdn']],
      notify    => Class['apache::service'],
    }

Reference

Files and directories

Locations of the important certificate files (i.e. "cert.example.com"):

  • /etc/acme.sh/certs/cert.example.com/cert.pem: the certificate
  • /etc/acme.sh/certs/cert.example.com/chain.pem: the CA chain
  • /etc/acme.sh/certs/cert.example.com/fullchain.pem: Certificate and CA chain in one file
  • /etc/acme.sh/certs/cert.example.com/cert.ocsp: OCSP Must-Staple extension data
  • /etc/acme.sh/keys/cert.example.com/private.key: Private key
  • /etc/acme.sh/keys/cert.example.com/fullchain_with_key.pem: All-in-one: certificate, CA chain, private key

Basic directory layout:

  • /etc/acme.sh/accounts: (Puppet Server) Private keys and other files related to ACME accounts
  • /etc/acme.sh/certs: Certificates, CA chains and OCSP files
  • /etc/acme.sh/configs: OpenSSL configuration and other files required for the CSR
  • /etc/acme.sh/csrs: Certificate signing requests (CSR)
  • /etc/acme.sh/home: (Puppet Server) Working directory for acme.sh
  • /etc/acme.sh/keys: Private keys for each certificate
  • /etc/acme.sh/results: (Puppet Server) Working directory, used to export certificates
  • /opt/acme.sh: (Puppet Server) Local copy of acme.sh (GIT repository)
  • /var/log/acme.sh/acme.log: (Puppet Server) acme.sh log file

Classes and parameters

Classes and parameters are documented in REFERENCE.md.

Limitations

Requires multiple Puppet runs

It takes several puppet runs to issue a certificate. Two Puppet runs are required to prepare the CSR on your Puppet node. Two more Puppet runs on your Puppet Server are required to sign the certificate and send it to PuppetDB. Now it's ready to be collected by your Puppet node.

The process is seamless for certificate renewals, but it takes a little time to issue a new certificate.

HTTP-01 challenge type untested

The HTTP-01 challenge type is theoretically supported, but it is untested with this module. Some additional parameters may be missing. Feel free to report issues or suggest enhancements.

Rebuilding nodes

When rebuilding or reinstalling an existing node, the module will be unable to create new or update existing certificates for this node. Instead a key mismatch will occur, because an entirely new private key will be created on the node.

There is currently no way to fix this automatically #6.

The old files can be manually cleaned on the Puppet Server by running something like this:

find /etc/acme.sh -name '*NODENAME*' -type f -delete

Besides that it may also be necessary to purge the old PuppetDB contents for this node.

OS Compatibility

This module was tested on CentOS/RedHat, Ubuntu/Debian and FreeBSD. Please open a new issue if your operating system is not supported yet, and provide information about problems or missing features.

Development

Please use the GitHub issues functionality to report any bugs or requests for new features. Feel free to fork and submit pull requests for potential contributions.

Fork

This module is a fork of the excellent bzed/bzed-letsencrypt. The fork was necessary in order to use acme.sh instead of dehydrated.

License

Copyright (C) 2017-2021 Frank Wall

Based on bzed/bzed-letsencrypt, Copyright 2017 Bernd Zeimetz.

Let's Encrypt is a trademark of the Internet Security Research Group. All rights reserved.

See the LICENSE file for further information.