diff --git a/hieradata/common.yaml b/hieradata/common.yaml index 17e2ae0..ad6c16f 100644 --- a/hieradata/common.yaml +++ b/hieradata/common.yaml @@ -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' diff --git a/hieradata/country/au/region/syd1.yaml b/hieradata/country/au/region/syd1.yaml index 1fda3f9..4175d66 100644 --- a/hieradata/country/au/region/syd1.yaml +++ b/hieradata/country/au/region/syd1.yaml @@ -1,3 +1,3 @@ --- timezone::timezone: 'Australia/Sydney' -profiles::pki::letsencrypt: ausyd1nxvm1021.main.unkin.net +certbot::client::webserver: ausyd1nxvm1021.main.unkin.net diff --git a/hieradata/country/au/region/syd1/infra/halb/haproxy.yaml b/hieradata/country/au/region/syd1/infra/halb/haproxy.yaml index c6e3cd1..63f1116 100644 --- a/hieradata/country/au/region/syd1/infra/halb/haproxy.yaml +++ b/hieradata/country/au/region/syd1/infra/halb/haproxy.yaml @@ -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 diff --git a/hieradata/roles/infra/pki/certbot.eyaml b/hieradata/roles/infra/pki/certbot.eyaml index d749727..12da70b 100644 --- a/hieradata/roles/infra/pki/certbot.eyaml +++ b/hieradata/roles/infra/pki/certbot.eyaml @@ -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] diff --git a/hieradata/roles/infra/pki/certbot.yaml b/hieradata/roles/infra/pki/certbot.yaml index d450604..40d8cba 100644 --- a/hieradata/roles/infra/pki/certbot.yaml +++ b/hieradata/roles/infra/pki/certbot.yaml @@ -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 diff --git a/modules/certbot/lib/facter/certbot_available_certs.rb b/modules/certbot/lib/facter/certbot_available_certs.rb new file mode 100644 index 0000000..cfbe2af --- /dev/null +++ b/modules/certbot/lib/facter/certbot_available_certs.rb @@ -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 diff --git a/site/profiles/manifests/certbot/cert.pp b/modules/certbot/manifests/cert.pp similarity index 86% rename from site/profiles/manifests/certbot/cert.pp rename to modules/certbot/manifests/cert.pp index 0496095..f923769 100644 --- a/site/profiles/manifests/certbot/cert.pp +++ b/modules/certbot/manifests/cert.pp @@ -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, diff --git a/modules/certbot/manifests/client.pp b/modules/certbot/manifests/client.pp new file mode 100644 index 0000000..3ca6ef3 --- /dev/null +++ b/modules/certbot/manifests/client.pp @@ -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], + } + } +} diff --git a/modules/certbot/manifests/client/cert.pp b/modules/certbot/manifests/client/cert.pp new file mode 100644 index 0000000..b4773dd --- /dev/null +++ b/modules/certbot/manifests/client/cert.pp @@ -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.': } + } +} diff --git a/site/profiles/manifests/certbot/haproxy.pp b/modules/certbot/manifests/haproxy.pp similarity index 74% rename from site/profiles/manifests/certbot/haproxy.pp rename to modules/certbot/manifests/haproxy.pp index 5fa264b..ea61ad5 100644 --- a/site/profiles/manifests/certbot/haproxy.pp +++ b/modules/certbot/manifests/haproxy.pp @@ -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', diff --git a/modules/certbot/manifests/init.pp b/modules/certbot/manifests/init.pp new file mode 100644 index 0000000..a32914f --- /dev/null +++ b/modules/certbot/manifests/init.pp @@ -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 + +} diff --git a/modules/certbot/manifests/letsencrypt.pp b/modules/certbot/manifests/letsencrypt.pp new file mode 100644 index 0000000..29b6c47 --- /dev/null +++ b/modules/certbot/manifests/letsencrypt.pp @@ -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'], + } +} diff --git a/site/profiles/manifests/certbot/nginx.pp b/modules/certbot/manifests/nginx.pp similarity index 81% rename from site/profiles/manifests/certbot/nginx.pp rename to modules/certbot/manifests/nginx.pp index 87d2cee..5170aff 100644 --- a/site/profiles/manifests/certbot/nginx.pp +++ b/modules/certbot/manifests/nginx.pp @@ -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) diff --git a/modules/certbot/manifests/selinux.pp b/modules/certbot/manifests/selinux.pp new file mode 100644 index 0000000..71e2c70 --- /dev/null +++ b/modules/certbot/manifests/selinux.pp @@ -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"], + } + } +} diff --git a/modules/certbot/templates/certbot-syncer.service.epp b/modules/certbot/templates/certbot-syncer.service.epp new file mode 100644 index 0000000..4123ffe --- /dev/null +++ b/modules/certbot/templates/certbot-syncer.service.epp @@ -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 diff --git a/modules/certbot/templates/certbot-syncer.timer.epp b/modules/certbot/templates/certbot-syncer.timer.epp new file mode 100644 index 0000000..52903b8 --- /dev/null +++ b/modules/certbot/templates/certbot-syncer.timer.epp @@ -0,0 +1,9 @@ +[Unit] +Description=certbot-syncer timer + +[Timer] +OnCalendar=hourly +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/site/profiles/manifests/certbot/init.pp b/site/profiles/manifests/certbot/init.pp deleted file mode 100644 index e03e311..0000000 --- a/site/profiles/manifests/certbot/init.pp +++ /dev/null @@ -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 - -} diff --git a/site/profiles/manifests/certbot/letsencrypt.pp b/site/profiles/manifests/certbot/letsencrypt.pp deleted file mode 100644 index be9299e..0000000 --- a/site/profiles/manifests/certbot/letsencrypt.pp +++ /dev/null @@ -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, - } - } -} diff --git a/site/profiles/manifests/haproxy/server.pp b/site/profiles/manifests/haproxy/server.pp index b16da8e..b19ab18 100644 --- a/site/profiles/manifests/haproxy/server.pp +++ b/site/profiles/manifests/haproxy/server.pp @@ -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 diff --git a/site/profiles/manifests/pki/letsencrypt.pp b/site/profiles/manifests/pki/letsencrypt.pp deleted file mode 100644 index f639673..0000000 --- a/site/profiles/manifests/pki/letsencrypt.pp +++ /dev/null @@ -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], - } - } -} diff --git a/site/roles/manifests/infra/pki/certbot.pp b/site/roles/manifests/infra/pki/certbot.pp index e1cc2e6..357d1a6 100644 --- a/site/roles/manifests/infra/pki/certbot.pp +++ b/site/roles/manifests/infra/pki/certbot.pp @@ -6,6 +6,5 @@ class roles::infra::pki::certbot { }else{ include profiles::defaults include profiles::base - include profiles::certbot::init } }