Comments (21)
I've worked around this with a custom fact that returns the live certificate directories:
require "pathname"
Facter.add(:letsencrypt_live) do
setcode do
Pathname.new("/etc/letsencrypt/live").children.select(&:directory?).collect(&:basename)
end
end
then in puppet only enabling the ssl server when it's in the list of certificates:
::apache::vhost {"${hostname} nossl":
# ...
}
::letsencrypt::certonly {$hostname:
require => Service[httpd],
# ...
}
if $hostname in $::letsencrypt_live {
::apache::vhost{"${hostname} ssl":
# ...
}
}
This does require two runs of Puppet before the SSL host is live
from puppet-letsencrypt.
That's a good point. Alright I'm convinced now ;-)
from puppet-letsencrypt.
@hoffereka I have the same issue, did you solve it?
from puppet-letsencrypt.
I've got it working in a single run, but it's not pretty. This sample code is for ubuntu 16.04. The key parts are running letsencrypt between the ssl and non-ssl vhosts, and restarting apache - the puppet default mpm is different from the ubuntu package's default mpm.
class { ::letsencrypt:
config => {
email => '[email protected]',
server => 'https://acme-staging.api.letsencrypt.org/directory',
}
}
class { ::apache:
default_vhost => false,
}
#This fixes:
# AH00534: apache2: Configuration error: The MPM cannot be changed during restart.
#The letsencrypt plugin only sends a SIGUSR1, which is insufficient
exec {'Restart Apache for MPM changes':
command => '/bin/systemctl restart apache2.service',
refreshonly => true,
}
Apache::Vhost<|ssl == false|> ~> Exec['Restart Apache for MPM changes'] -> Letsencrypt::Certonly<||> -> Apache::Vhost<|ssl == true|>
package {'python-letsencrypt-apache':
ensure => present,
before => Letsencrypt::Certonly['test.domain.example'],
}
letsencrypt::certonly {'test.domain.example':
domains => ['test.domain.example'],
plugin => 'apache',
additional_args => ['--debug'],
}
apache::vhost {'test.domain.example_http':
servername => 'test.domain.example',
port => 80,
docroot => '/var/www',
redirect_status => 'permanent',
redirect_dest => 'https://test.domain.example/',
}
apache::vhost {'test.domain.example_https':
servername => 'test.domain.example',
port => 443,
docroot => '/var/www',
ssl => true,
ssl_cert => '/etc/letsencrypt/live/test.domain.example/fullchain.pem',
ssl_key => '/etc/letsencrypt/live/test.domain.example/privkey.pem',
}
from puppet-letsencrypt.
I vote that this fact be added to the module proper. It'd be useful in various ways.
from puppet-letsencrypt.
@minorOffense Would you be able to create a PR?
from puppet-letsencrypt.
the fact returns a hash of live certificates. Cool.
Bonus point if the fact returns certificate information like domains, validity etc.
from puppet-letsencrypt.
Yeah I can create a PR. I'd just need to adjust the fact in case that directory isn't the same on all systems.
As for validity, domains, etc. that may be unnecessary since your ssl definition would have some of that info. I'll start with just the basic fact, see where that leads me.
from puppet-letsencrypt.
additional information could be useful for multi-domain certificates. then the check if the certificate is available is not enough. The additional domain could be missing.
from puppet-letsencrypt.
I had to modify the fact to get it to work:
require 'pathname'
Facter.add(:letsencrypt_live) do
setcode do
livedir = Pathname.new('/etc/letsencrypt/live')
if !livedir.directory?
[]
else
(livedir.children
.select(&:directory?)
.collect(&:basename)
.map { |name| name.to_s })
end
end
end
Without the final map, the array contains pathname objects rather than strings:
[root@rev-ru ~]# /opt/puppetlabs/puppet/bin/irb
irb(main):001:0> require 'pathname'
=> true
irb(main):002:0> Pathname.new("/etc/letsencrypt/live").children.select(&:directory?).collect(&:basename)
=> [#<Pathname:rev-ru.aws.focusvision.com>]
irb(main):003:0> Pathname.new("/etc/letsencrypt/live").children.select(&:directory?).collect(&:basename).map { |name| name.to_s }
=> ["rev-ru.aws.focusvision.com"]
[edit] can't return
from setcode
... fixed.
from puppet-letsencrypt.
It's good to note that certbot can also operate from /etc/certbot
instead of /etc/letsencrypt
but will prefer the latter if it exists. At least on my Debian box.
from puppet-letsencrypt.
Something like this could implement the behavior @ekohl describes:
require 'pathname'
Facter.add(:letsencrypt_live) do
setcode do
livedir = ([ '/etc/letsencrypt/live', '/etc/certbot/live' ]
.map { |path| Pathname.new path }
.find(&:directory?))
if livedir.nil? then []
else (livedir.children
.select(&:directory?)
.collect(&:basename)
.map(&:to_s))
end
end
end
Sorry about multiple edits... Ruby is not my primary language.
from puppet-letsencrypt.
@cwells I think you can also use .find()
instead of .select()[0]
from puppet-letsencrypt.
@ekohl updated, thanks.
from puppet-letsencrypt.
Okay, per @pgassmann's comment, this fact generates a hash indexed by SAN, referencing parent directory of certificate.
require 'openssl'
require 'pathname'
Facter.add(:letsencrypt_directory) do
setcode do
certs = {}
# locate the certificate repository
livedir = ([ '/etc/letsencrypt/live', '/etc/certbot/live' ]
.map { |path| Pathname.new path }
.find(&:directory?))
Pathname.new(livedir).children.select(&:directory?).each do |path|
pem = File.join(path, 'cert.pem')
cert = OpenSSL::X509::Certificate.new(File.new(pem).read)
san = cert.extensions.find { |e| e.oid == 'subjectAltName' }
names = san.value.split(',').map { |entry| entry.split(':')[1] }
names.each do |n|
certs[n] = path.to_s
end
end unless livedir.nil?
certs
end
end
returns something like:
{
'san1.domain.com' => '/etc/letsencrypt/live/san.domain.com',
'san2.domain.com' => '/etc/letsencrypt/live/san.domain.com',
'www.domain.com' => '/etc/letsencrypt/live/www.domain.com'
}
from puppet-letsencrypt.
... and a companion function that will lookup a CN (and also match wildcard certs).
Puppet::Functions.create_function(:letsencrypt_lookup) do
def letsencrypt_lookup(cn)
domain = cn.split('.', 2)[1]
wildcard = "*.#{domain}"
certs = closure_scope['facts'].fetch('letsencrypt_directory', nil)
certs.fetch(cn, certs.fetch(wildcard, nil)) unless certs.nil?
end
end
So now in your manifest, you can write:
$cert = letsencrypt_lookup($hostname)
if $cert {
nginx::resource::server { "ssl-${hostname}":
ssl => true,
ssl_port => 443,
ssl_cert => "${cert}/fullchain.pem",
ssl_key => "${cert}/privkey.pem"
# ...
}
}
from puppet-letsencrypt.
Hi - I'm still a bit of a Puppet n00b, but I'm looking to do the same thing using nginx instead of apache, using the Vox nginx module. Can the same theory be applied as above from @benjunmun ?
Help/suggestions much appreciated! :)
from puppet-letsencrypt.
@cmseal Take a look at my module letsencrypt_nginx for reference or use it directly
from puppet-letsencrypt.
I've been testing the fact for seeing if the cert is live or not. I'm still not convinced that all the extra metadata about the certs is useful in puppet (or the extra processing time to generate those facts especially with lots of certs).
But here's a reference implementation of an apache::vhosts class we've started using which uses the self-signed certs until the signed ones become available. This only works with Puppet 5 and the latest apache modules.
The way it works is you pass a custom boolean option called "manage_cert" through your vhost. It then creates the redirect from http to https, sets the SSL cert location, cleans up some config and then hands it off to the apache module to actually create the configuration.
Works great, within two puppet runs you have valid SSL certificates in place. Any suggestions or improvements are welcome.
In any case I'll be prepping the merge request to add the fact about the certs.
# Wrapper class that adds automatic SSL management.
class coldfront::apache::vhosts {
include ::apache::vhosts # Also allows for backwards compatibility to dropfort vms from the v3 provisioner.
# Grab the defined vhosts.
$vhosts = lookup('coldfront::apache::vhosts', {value_type => Hash, merge => deep, default_value => {}})
# Scan for SSL enabled vhosts.
$auto_ssl = $vhosts.filter |$vhost_indexes, $vhost_values| {
$vhost_values['ssl'] == true and $vhost_values['manage_cert'] == true
}
# Only require letsencrypt if the auto signing is requested
if ! empty($auto_ssl) {
include ::letsencrypt
}
# Create the certs
$auto_ssl.each |$key, $value| {
# @todo make the system command platform agnostic.
::letsencrypt::certonly {"${value['servername']}":
manage_cron => true,
plugin => 'webroot',
webroot_paths => ['/var/www/html'],
suppress_cron_output => true,
cron_success_command => '/bin/systemctl graceful httpd',
}
}
# Create redirects from insecure port.
$auto_redirect_array = $auto_ssl.map |$index, $value| {
{"insecure-${index}" => {
'servername' => $value['servername'],
'docroot' => $value['docroot'],
'port' => 80,
'proxy_pass' => [
{'path' => '/.well-known/acme-challenge/', 'url' => '!'},
],
'aliases' => [
{
'alias' => '/.well-known/acme-challenge/',
'path' => '/var/www/html/.well-known/acme-challenge/'
},
],
'directories' => [
{
'path' => '/var/www/html/.well-known/acme-challenge/',
'allow'=> 'from all',
'allow_override' => 'None',
'options' => [
'Indexes',
'FollowSymLinks',
'MultiViews'
],
},
],
'rewrites' => [
{
'rewrite_cond' => ['%{REQUEST_URI} !\.well-known/acme-challenge'],
'rewrite_rule' => ['^(.*) https://%{SERVER_NAME}$1 [R=301,L]'],
},
],
}
}
}
# Convert to hash.
$auto_redirect = $auto_redirect_array.reduce |$memo, $value| {
$memo + $value
}
# Get the list of certs that are live.
$auto_ssl_live = $auto_ssl.filter |$live_indexes, $live_values| {$live_values['servername'] in $::letsencrypt_live}
# Update the cert info for each live vhost.
$auto_ssl_vhosts_array = $auto_ssl_live.map |$index, $values| {
{$index => deep_merge($values,
{
'ssl_cert' => "/etc/letsencrypt/live/${values['servername']}/cert.pem",
'ssl_key' => "/etc/letsencrypt/live/${values['servername']}/privkey.pem",
'ssl_chain' => "/etc/letsencrypt/live/${values['servername']}/chain.pem",
}
)}
}
#https://github.com/puppetlabs/puppetlabs-stdlib#deep_merge
# Convert map array to hash.
$auto_ssl_vhosts = $auto_ssl_vhosts_array.reduce |$memo, $value| {
$memo + $value
}
## We'll need a few layers of merges here.
# 1. Combine the automatic ssl hosts
# 2. Combine the automatic ssl hosts with valid certs
# 3. Combine the automatic ssl redirect hosts
# 4. Combine all the aliased insecure hosts with the main list.
# 4. Combine the untouched vhosts with the updated settings
$managed_ssl_vhosts = deep_merge($auto_ssl, $auto_ssl_vhosts)
$managed_vhosts = deep_merge($vhosts, $auto_redirect)
$processed_vhosts = deep_merge($managed_vhosts, $managed_ssl_vhosts)
# Delete custom vhost options.
$cleaned_vhosts_array = $processed_vhosts.map |$processed_index, $processed_values| {
{$processed_index => delete($processed_values, ['manage_cert'])}
}
# Convert map array to hash.
$cleaned_vhosts = $cleaned_vhosts_array.reduce |$memo, $value| {
$memo + $value
}
if ! empty($cleaned_vhosts) {
create_resources('::apache::vhost', $cleaned_vhosts)
}
}
from puppet-letsencrypt.
@minorOffense "I'm still not convinced that all the extra metadata about the certs is useful in puppet (or the extra processing time to generate those facts especially with lots of certs)."
The "extra" metadata is a list of subject alternative names (SAN) listed in the certificate (including wildcards). You cannot determine which domains a certificate is valid for simply by checking the filename, as the filename is only based on the common name (CN) and there may be additional names present within the cert.
from puppet-letsencrypt.
The initial question no longer seems relevant.
I am using this Puppet module with Apache without issues.
Feel free to reopen if needed.
from puppet-letsencrypt.
Related Issues (20)
- update metadata.json
- typo in example HOT 2
- certbot has a python problem on centos 7 HOT 8
- Cut new version with puppet-epel dependency? HOT 1
- RHEL8 support for dns-rfc2136
- Logrotate for the letsencrypt logs HOT 1
- certbot-auto no longer works on any OS HOT 2
- Adding domains to existing certificate leads to duplicate certs/renewal configs with pattern <cert>-0001,2 etc HOT 3
- Raise compatible puppet version from <7.0 to <8.0 HOT 2
- Drop VCS install method support
- Recent update causes problems with the nginx plugin HOT 1
- CONFIGDIR/renwal/domain.conf not updated HOT 2
- Manage Cron parameter on letsencrypt::certonly will not cleanup resources. HOT 2
- feature request: cron_after_command
- `register-unsafely-without-email` config key is kept when turning `unsafe_registration` back to `false`
- Could not find class ::epel when declaring Letsencrypt class HOT 1
- New release please? HOT 3
- letsencrypt-domain-validation case sensitivity
- Documentation/examples for certonly `suppress_cron_output` not updated after removal of parameter HOT 1
- Circular dependency caused by nginx plugin HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from puppet-letsencrypt.