0

I want to setup and configure Apache via Puppet and request a SSL certificate with acme_tiny.py. My Puppet classes and resources for the web server and for acme-tiny work in most case, except for the first start.

Unfortunately, acme_tiny needs a running web server, which is not starting until after the acme_tiny resource has successfully completed. I imagine that the flow should be something like this:

Install Apache -> Start Apache -> Configure a HTTP vhost -> Reload Apache -> Run acme_tiny -> Configure the HTTPS vhost -> Reload Apache

The problem is that the resource "apache2 reload" can only exist once in Puppet and I get a dependency cycle if I order the acme_tiny resource in between. Additionally, the resource is also managed by the puppetlabs/apache module, every time a new vhost is created, but only applied last. Currently, the flow is like this:

Install Apache -> Start Apache -> Configure a HTTP vhost -> Run acme_tiny (fails) -> Configure the HTTPS vhost (skipped due to failed dependencies) -> Configure everything else -> Reload Apache (skipped due to failed dependencies)

If I then manually start Apache2 after the first run, it all works fine: The certificate is retrieved, the HTTPS vhost is created and the web server is reloaded. Unfortunately it does not work without manual intervention.

My acme-tiny resource looks like this:

exec { "${url}.crt":
  command   => "acme_tiny.py --quiet --account-key ./${url}_account.key --csr ./${url}.csr --acme-dir /home/web/${url}/www > ${url}.crt",
  path      => [ '/usr/bin', '/usr/local/bin' ],
  cwd       => $profile::apache::params::ssl_dir,
  require   => File['acme_tiny.py'],
  subscribe => File["${profile::apache::params::ssl_dir}/${url}.csr"],
  notify    => Service['apache2'],
}

Does anybody have an idea how to fix this? Ideally, it should all be completed in one Puppet run, second best option would be if a second Puppet run is needed without manual intervention needed. Essentially, if acme_tiny fails, only the configuration of the HTTPS vhost should fail, but not reloading Apache.

  • Can you show us your puppet manifest ? With the dependencies you already applied. So we can try to find what is wrong. – Wee Jun 27 '17 at 08:24
  • My apache manifest is really long with many resources, classes and ruby code mixed in between :D It is really hard to insert it here. – Stigr Vulferam Jun 27 '17 at 13:35

1 Answers1

0

I managed to do it with a little dirty hack, but at least it works.

I saw that Apache is already running in the default Debian setup after Puppet has installed the package, but Puppet reconfigures it (and issues an apache reload at the end). Luckily, the web server already responds to all requests by serving static files.

I have then split the acme_tiny resource into two parts: Setup and renewal: For setup, Puppet first creates the folder /var/www/html/.well-known/acme-challenge and then executes the following exec:

# Request a new certificate if the crt file does not yet exist
exec { "${url}.crt initial":
  command   => "acme_tiny.py --quiet --account-key ./${url}_account.key --csr ./${url}.csr --acme-dir /var/www/html/.well-known/acme-challenge > ${url}.crt",
  creates   => "${profile::apache::params::ssl_dir}/${url}.crt",
  path      => [ '/usr/bin', '/usr/local/bin' ],
  cwd       => $profile::apache::params::ssl_dir,
  require   => [ File['acme_tiny.py'], File['/var/www/html/.well-known/acme-challenge'] ],
  subscribe => File["${profile::apache::params::ssl_dir}/${url}.csr"],
  notify    => Service['apache2'],
}

This resource is not executed if the certificate already exists (hence the createsparameter).

On every subsequent run, the renewal exec will be executed:

 exec { "${url}.crt renewal":
  command   => "acme_tiny.py --quiet --account-key ./${url}_account.key --csr ./${url}.csr --acme-dir /home/web/${url}/www > ${url}.crt",
  unless    => ["openssl x509 -checkend 2592000 -noout -in ${url}.crt",
                "test ! -f ${url}.crt" ],
  path      => [ '/usr/bin', '/usr/local/bin' ],
  cwd       => $profile::apache::params::ssl_dir,
  require   => File['acme_tiny.py'],
  subscribe => File["${profile::apache::params::ssl_dir}/${url}.csr"],
  notify    => Service['apache2'],
}

This exec only executes if the existing certificate is valid for less than a month and if the certificate already exists.

This should make the execs mutually exclusive, so that only one exec is executed in one Puppet run.

There are still some settings to make (a default vhost has to be created even after the initial setup, for example if you want to configure a second vhost into an already running webserver), but I currently it is working quite nicely.

This does not answer the problem with Puppet (as it currently depends on an Apache "feature"), but actually I highly doubt that it is possible with Puppet: A core feature of Puppet is that very resource can only be applied once.