feat: certbot reorg

- moved certbot into its own module
- added fact to list available certificates
- created systemd timer to rsync data to $data_dir/pub
- ensure the $data_dir/pub exists
- manage selinux for nginx
This commit is contained in:
Ben Vincent 2024-07-08 20:22:44 +10:00
parent 30ec8c1bb1
commit bd5164fed3
21 changed files with 232 additions and 80 deletions

View File

@ -132,7 +132,9 @@ lookup_options:
profiles::nginx::simpleproxy::locations:
merge:
strategy: deep
certbot::client::domains:
merge:
strategy: deep
facts_path: '/opt/puppetlabs/facter/facts.d'

View File

@ -1,3 +1,3 @@
---
timezone::timezone: 'Australia/Sydney'
profiles::pki::letsencrypt: ausyd1nxvm1021.main.unkin.net
certbot::client::webserver: ausyd1nxvm1021.main.unkin.net

View File

@ -201,3 +201,14 @@ profiles::pki::vault::alt_names:
profiles::haproxy::dns::cnames:
- au-syd1-pve.main.unkin.net
- au-syd1-pve-api.main.unkin.net
# letsencrypt certificates
certbot::client::domains:
- au-syd1-pve.main.unkin.net
- au-syd1-pve-api.main.unkin.net
- sonarr.main.unkin.net
- radarr.main.unkin.net
- lidarr.main.unkin.net
- readarr.main.unkin.net
- prowlarr.main.unkin.net
- fafflix.unkin.net

View File

@ -1,2 +1,2 @@
---
profiles::certbot::init::contact: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAJxDjhvXONEm7VoZ74dBxOPxFAw9RrI2WOK1P5YiIWiXUkoOhQpPzy0PUlI4970ActfTi9Kr9fnyZJWr/7TQ/5GQuYvVxMcfWbOmIOA+6CCjR/PWR06lWQuq7eTmwTzQjw7teFZrpXmqutAMNAUEAmPBBKNKfKbOaFz4IWwph1TuXtXDuveu/RE2+8znWukhF92DuFBJSuw6SMDympdbgceq/guQAInMjIXwmCIa7DWCWYDSKw04Ai8yDnYoqaNRs0acbZV6slH49i/cOE6GKTxO8+vR/3TkjEvKH8lY2l37ndH9+pe58arKflm/Inik0zy0TBnHq7/AMmEpRtV0usTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBUgafckUM981Pb6hn2/9KMgBAblakRJjULF7aZwx/PT09s]
certbot::contact: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAJxDjhvXONEm7VoZ74dBxOPxFAw9RrI2WOK1P5YiIWiXUkoOhQpPzy0PUlI4970ActfTi9Kr9fnyZJWr/7TQ/5GQuYvVxMcfWbOmIOA+6CCjR/PWR06lWQuq7eTmwTzQjw7teFZrpXmqutAMNAUEAmPBBKNKfKbOaFz4IWwph1TuXtXDuveu/RE2+8znWukhF92DuFBJSuw6SMDympdbgceq/guQAInMjIXwmCIa7DWCWYDSKw04Ai8yDnYoqaNRs0acbZV6slH49i/cOE6GKTxO8+vR/3TkjEvKH8lY2l37ndH9+pe58arKflm/Inik0zy0TBnHq7/AMmEpRtV0usTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBUgafckUM981Pb6hn2/9KMgBAblakRJjULF7aZwx/PT09s]

View File

@ -1,5 +1,9 @@
---
profiles::certbot::init::domains:
hiera_include:
- certbot
- profiles::pki::puppetcerts
certbot::domains:
- au-syd1-pve.main.unkin.net
- au-syd1-pve-api.main.unkin.net
- sonarr.main.unkin.net

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
Facter.add(:certbot_available_certs) do
confine enc_role: 'roles::infra::pki::certbot'
setcode do
certs_dir = '/etc/letsencrypt/live'
available_certs = []
if Dir.exist?(certs_dir)
Dir.children(certs_dir).each do |entry|
fullchain_pem = File.join(certs_dir, entry, 'fullchain.pem')
available_certs << entry if File.exist?(fullchain_pem)
end
end
available_certs.join(',')
end
end

View File

@ -1,5 +1,5 @@
# profiles::certbot::cert
define profiles::certbot::cert (
# certbot::cert
define certbot::cert (
Stdlib::Fqdn $domain,
Array $additional_args = ['--http-01-port=8888'],
Boolean $manage_cron = true,

View File

@ -0,0 +1,23 @@
class certbot::client (
Array[Stdlib::Fqdn] $domains,
Stdlib::Fqdn $webserver,
Stdlib::Absolutepath $data_dir = '/etc/pki/tls/letsencrypt/',
) {
mkdir::p {$data_dir:}
file { $data_dir:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}
$domains.each |$domain| {
certbot::client::cert {"${facts['networking']['fqdn']}_download_${domain}":
domain => $domain,
destination => "${data_dir}/${domain}",
webserver => $webserver,
require => File[$data_dir],
}
}
}

View File

@ -0,0 +1,51 @@
define certbot::client::cert (
Stdlib::Fqdn $domain,
Stdlib::Fqdn $webserver,
Stdlib::Absolutepath $destination = "/etc/pki/tls/letsencrypt/${domain}",
) {
file { $destination:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}
$cert_ready_nodes = puppetdb_query("
facts {
name = 'certbot_available_certs' and value ~ '${domain}' and certname = '${webserver}'
}"
)
# Define the certificate files
$cert_files = ['cert.pem', 'chain.pem', 'fullchain.pem', 'privkey.pem']
if !empty($cert_ready_nodes) {
$files_to_create = $cert_files.reduce({}) |$acc, $file| {
$acc + {
"${destination}/${file}" => {
ensure => 'file',
source => "https://${webserver}/${domain}/${file}",
owner => 'root',
group => 'root',
mode => '0644',
notify => Exec["concat_${domain}_certs"],
}
}
}
create_resources(file, $files_to_create)
exec { "concat_${domain}_certs":
command => "cat ${destination}/fullchain.pem ${destination}/privkey.pem > ${destination}/fullchain_combined.pem",
path => ['/bin', '/usr/bin'],
refreshonly => true,
require => [
File["${destination}/fullchain.pem"],
File["${destination}/privkey.pem"],
],
}
} else {
notify { 'Certificates are not yet ready on the generator server.': }
}
}

View File

@ -1,5 +1,5 @@
# profiles::certbot::haproxy
class profiles::certbot::haproxy {
# certbot::haproxy
class certbot::haproxy {
# export haproxy balancemember
profiles::haproxy::balancemember { "${facts['networking']['fqdn']}_8888":
service => 'be_letsencrypt',

View File

@ -0,0 +1,19 @@
# certbot::init
class certbot (
String $contact,
Array[Stdlib::Fqdn] $domains = [],
Stdlib::Absolutepath $data_root = '/var/www',
Stdlib::Fqdn $nginx_vhost = $facts['networking']['fqdn'],
Array[Stdlib::Host] $nginx_aliases = [],
Stdlib::Port $nginx_port = 80,
Stdlib::Port $nginx_ssl_port = 443,
Enum['http','https','both'] $nginx_listen_mode = 'https',
Enum['puppet', 'vault'] $nginx_cert_type = 'puppet',
) {
include certbot::nginx
include certbot::selinux
include certbot::haproxy
include certbot::letsencrypt
}

View File

@ -0,0 +1,37 @@
# certbot::letsencrypt
class certbot::letsencrypt (
String $contact = $certbot::contact,
Array[Stdlib::Fqdn] $domains = $certbot::domains,
Stdlib::Absolutepath $data_root = $certbot::data_root,
) {
class { 'letsencrypt':
configure_epel => false,
package_ensure => 'latest',
email => $contact,
}
# set location_environment
$location_environment = "${facts['country']}-${facts['region']}-${facts['environment']}"
# collect exported resources
Letsencrypt::Certonly <<| tag == $location_environment |>>
# statically defined certificate
$domains.each | $domain | {
certbot::cert {$domain:
domain => $domain,
require => Class['letsencrypt'],
}
}
systemd::timer { 'certbot-syncer.timer':
timer_content => epp('certbot/certbot-syncer.timer.epp'),
service_content => epp('certbot/certbot-syncer.service.epp', {
'data_root' => $data_root,
}),
active => true,
enable => true,
require => Class['letsencrypt'],
}
}

View File

@ -1,12 +1,12 @@
# profiles::certbot::nginx
class profiles::certbot::nginx (
Stdlib::Absolutepath $data_root = '/var/www/',
Stdlib::Fqdn $nginx_vhost = $facts['networking']['fqdn'],
Array[Stdlib::Host] $nginx_aliases = [],
Stdlib::Port $nginx_port = 80,
Stdlib::Port $nginx_ssl_port = 443,
Enum['http','https','both'] $nginx_listen_mode = 'https',
Enum['puppet', 'vault'] $nginx_cert_type = 'vault',
# certbot::nginx
class certbot::nginx (
Stdlib::Absolutepath $data_root = $certbot::data_root,
Stdlib::Fqdn $nginx_vhost = $certbot::nginx_vhost,
Array[Stdlib::Host] $nginx_aliases = $certbot::nginx_aliases,
Stdlib::Port $nginx_port = $certbot::nginx_port,
Stdlib::Port $nginx_ssl_port = $certbot::nginx_ssl_port,
Enum['http','https','both'] $nginx_listen_mode = $certbot::nginx_listen_mode,
Enum['puppet', 'vault'] $nginx_cert_type = $certbot::nginx_cert_type,
) {
# select the certificates to use based on cert type
@ -59,6 +59,8 @@ class profiles::certbot::nginx (
}
}
mkdir::p {"${data_root}/pub":}
# set the server_names
$server_names = unique([$facts['networking']['fqdn'], $nginx_vhost] + $nginx_aliases)

View File

@ -0,0 +1,28 @@
# certbot::selinux
class certbot::selinux (
Stdlib::Absolutepath $data_root = $certbot::data_root,
) {
if $::facts['os']['selinux']['config_mode'] == 'enforcing' {
# set httpd_sys_content_t to all files under the www_root
selinux::fcontext { "${data_root}/pub":
ensure => 'present',
seltype => 'httpd_sys_content_t',
pathspec => "${data_root}/pub(/.*)?",
}
# make sure we can connect to other hosts
selboolean { 'httpd_can_network_connect':
persistent => true,
value => 'on',
}
exec { "restorecon_${data_root}/pub":
path => ['/bin', '/usr/bin', '/sbin', '/usr/sbin'],
command => "restorecon -Rv ${data_root}/pub",
refreshonly => true,
subscribe => Selinux::Fcontext["${data_root}/pub"],
}
}
}

View File

@ -0,0 +1,10 @@
[Unit]
Description=certbot-syncer service
[Service]
Type=oneshot
ExecStart=/usr/bin/rsync --chmod=D2755,F644 -aL /etc/letsencrypt/live/ <%= $data_root %>/pub/
User=root
Group=root
PermissionsStartOnly=false
PrivateTmp=no

View File

@ -0,0 +1,9 @@
[Unit]
Description=certbot-syncer timer
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target

View File

@ -1,11 +0,0 @@
# profiles::certbot::init
class profiles::certbot::init (
String $contact,
Array[Stdlib::Fqdn] $domains = [],
) {
include profiles::certbot::nginx
include profiles::certbot::haproxy
include profiles::certbot::letsencrypt
}

View File

@ -1,25 +0,0 @@
# profiles::certbot::letsencrypt
class profiles::certbot::letsencrypt (
String $contact = $profiles::certbot::init::contact,
Array[Stdlib::Fqdn] $domains = $profiles::certbot::init::domains,
) {
class { 'letsencrypt':
configure_epel => false,
package_ensure => 'latest',
email => $contact,
}
# set location_environment
$location_environment = "${facts['country']}-${facts['region']}-${facts['environment']}"
# collect exported resources
Letsencrypt::Certonly <<| tag == $location_environment |>>
# statically defined certificate
$domains.each | $domain | {
profiles::certbot::cert {$domain:
domain => $domain,
}
}
}

View File

@ -48,6 +48,7 @@ class profiles::haproxy::server (
require => Class['profiles::haproxy::selinux']
}
include certbot::client # download certbot certs
include profiles::haproxy::certlist # manage the certificate list file
include profiles::haproxy::mappings # manage the domain to backend mappings
include profiles::haproxy::ls_stats # default status listener

View File

@ -1,26 +0,0 @@
define profiles::pki::letsencrypt (
Stdlib::Fqdn $webserver,
Stdlib::Fqdn $domain,
Stdlib::Absolutepath $destination = "/etc/pki/tls/letsencrypt/${domain}",
) {
file { $destination:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}
$cert_files = ['cert.pem', 'chain.pem', 'fullchain.pem', 'privkey.pem']
$cert_files.each |String $file| {
file { "${destination}/${file}":
ensure => file,
source => "https://${webserver}/${domain}/${file}",
owner => 'root',
group => 'root',
mode => '0644',
require => File[$destination],
}
}
}

View File

@ -6,6 +6,5 @@ class roles::infra::pki::certbot {
}else{
include profiles::defaults
include profiles::base
include profiles::certbot::init
}
}