diff --git a/REFERENCE.md b/REFERENCE.md index ab343fd7..55fa4328 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -10,6 +10,7 @@ * [`letsencrypt`](#letsencrypt): Install and configure Certbot, the LetsEncrypt client * [`letsencrypt::install`](#letsencryptinstall): Installs the Let's Encrypt client. +* [`letsencrypt::plugin::dns_cloudflare`](#letsencryptplugindns_cloudflare): Installs and configures the dns-cloudflare plugin * [`letsencrypt::plugin::dns_rfc2136`](#letsencryptplugindns_rfc2136): Installs and configures the dns-rfc2136 plugin * [`letsencrypt::plugin::dns_route53`](#letsencryptplugindns_route53): Installs and configures the dns-route53 plugin * [`letsencrypt::plugin::nginx`](#letsencryptpluginnginx): install and configure the Let's Encrypt nginx plugin @@ -329,6 +330,84 @@ Name of package to use when installing the client package. Default value: `$letsencrypt::package_name` +### `letsencrypt::plugin::dns_cloudflare` + +This class installs and configures the Let's Encrypt dns-cloudflare plugin. +https://certbot-dns-cloudflare.readthedocs.io + +#### Parameters + +The following parameters are available in the `letsencrypt::plugin::dns_cloudflare` class: + +* [`package_name`](#package_name) +* [`api_key`](#api_key) +* [`api_token`](#api_token) +* [`email`](#email) +* [`config_dir`](#config_dir) +* [`manage_package`](#manage_package) +* [`propagation_seconds`](#propagation_seconds) +* [`config_path`](#config_path) + +##### `package_name` + +Data type: `Optional[String[1]]` + +The name of the package to install when $manage_package is true. + +Default value: ``undef`` + +##### `api_key` + +Data type: `Optional[String[1]]` + +Optional string, cloudflare api key value for authentication. + +Default value: ``undef`` + +##### `api_token` + +Data type: `Optional[String[1]]` + +Optional string, cloudflare api token value for authentication. + +Default value: ``undef`` + +##### `email` + +Data type: `Optional[String[1]]` + +Optional string, cloudflare account email address, used in conjunction with api_key. + +Default value: ``undef`` + +##### `config_dir` + +The path to the configuration directory. + +##### `manage_package` + +Data type: `Boolean` + +Manage the plugin package. + +Default value: ``true`` + +##### `propagation_seconds` + +Data type: `Integer` + +Number of seconds to wait for the DNS server to propagate the DNS-01 challenge. + +Default value: `10` + +##### `config_path` + +Data type: `Stdlib::Absolutepath` + + + +Default value: `"${letsencrypt::config_dir}/dns-cloudflare.ini"` + ### `letsencrypt::plugin::dns_rfc2136` This class installs and configures the Let's Encrypt dns-rfc2136 plugin. diff --git a/data/Debian-family.yaml b/data/Debian-family.yaml index d0641b25..c52a03b6 100644 --- a/data/Debian-family.yaml +++ b/data/Debian-family.yaml @@ -1,3 +1,4 @@ --- letsencrypt::plugin::dns_rfc2136::package_name: 'python3-certbot-dns-rfc2136' letsencrypt::plugin::dns_route53::package_name: 'python3-certbot-dns-route53' +letsencrypt::plugin::dns_cloudflare::package_name: 'python3-certbot-dns-cloudflare' diff --git a/data/FreeBSD-family.yaml b/data/FreeBSD-family.yaml index 78992374..c1f6af2f 100644 --- a/data/FreeBSD-family.yaml +++ b/data/FreeBSD-family.yaml @@ -4,3 +4,4 @@ letsencrypt::config_dir: '/usr/local/etc/letsencrypt' letsencrypt::cron_owner_group: 'wheel' letsencrypt::plugin::dns_rfc2136::package_name: 'py38-certbot-dns-rfc2136' letsencrypt::plugin::dns_route53::package_name: 'py38-certbot-dns-route53' +letsencrypt::plugin::dns_cloudflare::package_name: 'py38-certbot-dns-cloudflare' diff --git a/data/RedHat-family.yaml b/data/RedHat-family.yaml index 59fca34f..35f52041 100644 --- a/data/RedHat-family.yaml +++ b/data/RedHat-family.yaml @@ -2,3 +2,4 @@ letsencrypt::configure_epel: true letsencrypt::plugin::dns_rfc2136::package_name: 'python3-certbot-dns-rfc2136' letsencrypt::plugin::dns_route53::package_name: 'python3-certbot-dns-route53' +letsencrypt::plugin::dns_cloudflare::package_name: 'python3-certbot-dns-cloudflare' diff --git a/data/os/CentOS/7.yaml b/data/os/CentOS/7.yaml index 1aba5e01..3920067f 100644 --- a/data/os/CentOS/7.yaml +++ b/data/os/CentOS/7.yaml @@ -1,4 +1,5 @@ --- letsencrypt::plugin::dns_rfc2136::package_name: 'python2-certbot-dns-rfc2136' letsencrypt::plugin::dns_route53::package_name: 'python2-certbot-dns-route53' +letsencrypt::plugin::dns_cloudflare::package_name: 'python2-certbot-dns-cloudflare' letsencrypt::plugin::nginx::package_name: 'python2-certbot-nginx' diff --git a/data/os/RedHat/7.yaml b/data/os/RedHat/7.yaml index 1aba5e01..3920067f 100644 --- a/data/os/RedHat/7.yaml +++ b/data/os/RedHat/7.yaml @@ -1,4 +1,5 @@ --- letsencrypt::plugin::dns_rfc2136::package_name: 'python2-certbot-dns-rfc2136' letsencrypt::plugin::dns_route53::package_name: 'python2-certbot-dns-route53' +letsencrypt::plugin::dns_cloudflare::package_name: 'python2-certbot-dns-cloudflare' letsencrypt::plugin::nginx::package_name: 'python2-certbot-nginx' diff --git a/manifests/certonly.pp b/manifests/certonly.pp index 3d148959..3e24117b 100644 --- a/manifests/certonly.pp +++ b/manifests/certonly.pp @@ -168,6 +168,17 @@ $plugin_args = ["--cert-name '${cert_name}'"] + $_plugin_args } + 'dns-cloudflare': { + require letsencrypt::plugin::dns_cloudflare + $_domains = join($domains, '\' -d \'') + $plugin_args = [ + "--cert-name '${cert_name}' -d '${_domains}'", + '--dns-cloudflare', + "--dns-cloudflare-credentials ${letsencrypt::plugin::dns_cloudflare::config_path}", + "--dns-cloudflare-propagation-seconds ${letsencrypt::plugin::dns_cloudflare::propagation_seconds}", + ] + } + 'dns-rfc2136': { require letsencrypt::plugin::dns_rfc2136 $_domains = join($domains, '\' -d \'') @@ -242,7 +253,8 @@ $verify_domains = join(unique($domains), '\' \'') if $ensure == 'present' { - $exec_ensure = { 'unless' => "/usr/local/sbin/letsencrypt-domain-validation ${live_path} '${verify_domains}'" } + $exec_ensure = { 'unless' => ['test ! -f /usr/local/sbin/letsencrypt-domain-validation', + "/usr/local/sbin/letsencrypt-domain-validation ${live_path} '${verify_domains}'"] } } else { $exec_ensure = { 'onlyif' => "/usr/local/sbin/letsencrypt-domain-validation ${live_path} '${verify_domains}'" } } diff --git a/manifests/plugin/dns_cloudflare.pp b/manifests/plugin/dns_cloudflare.pp new file mode 100644 index 00000000..dc28ade8 --- /dev/null +++ b/manifests/plugin/dns_cloudflare.pp @@ -0,0 +1,67 @@ +# @summary Installs and configures the dns-cloudflare plugin +# +# This class installs and configures the Let's Encrypt dns-cloudflare plugin. +# https://certbot-dns-cloudflare.readthedocs.io +# +# @param package_name The name of the package to install when $manage_package is true. +# @param api_key +# Optional string, cloudflare api key value for authentication. +# @param api_token +# Optional string, cloudflare api token value for authentication. +# @param email +# Optional string, cloudflare account email address, used in conjunction with api_key. +# @param config_dir The path to the configuration directory. +# @param manage_package Manage the plugin package. +# @param propagation_seconds Number of seconds to wait for the DNS server to propagate the DNS-01 challenge. +# +class letsencrypt::plugin::dns_cloudflare ( + Optional[String[1]] $package_name = undef, + Optional[String[1]] $api_key = undef, + Optional[String[1]] $api_token = undef, + Optional[String[1]] $email = undef, + Stdlib::Absolutepath $config_path = "${letsencrypt::config_dir}/dns-cloudflare.ini", + Boolean $manage_package = true, + Integer $propagation_seconds = 10, +) { + require letsencrypt::install + + if ! $api_key and ! $api_token { + fail('No authentication method provided, please specify either api_token or api_key and api_email.') + } + + if $manage_package { + if ! $package_name { + fail('No package name provided for certbot dns cloudflare plugin.') + } + + package { $package_name: + ensure => installed, + } + } + + if $api_token { + $ini_vars = { + dns_cloudflare_api_token => $api_token, + } + } + else { + if ! $email { + fail('Cloudflare email not provided for specified api_key.') + } + + $ini_vars = { + dns_cloudflare_api_key => $api_key, + dns_cloudflare_email => $email, + } + } + + file { $config_path: + ensure => file, + owner => 'root', + group => 'root', + mode => '0400', + content => epp('letsencrypt/ini.epp', { + vars => { '' => $ini_vars }, + }), + } +} diff --git a/spec/acceptance/letsencrypt_plugin_dns_cloudflare_spec.rb b/spec/acceptance/letsencrypt_plugin_dns_cloudflare_spec.rb new file mode 100644 index 00000000..bda15773 --- /dev/null +++ b/spec/acceptance/letsencrypt_plugin_dns_cloudflare_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'letsencrypt::plugin::dns_cloudflare' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<-PUPPET + class { 'letsencrypt' : + email => 'letsregister@example.com', + config => { + 'server' => 'https://acme-staging-v02.api.letsencrypt.org/directory', + }, + } + class { 'letsencrypt::plugin::dns_cloudflare': + api_token => 'dummy-cloudflare-api-token', + } + PUPPET + end + end + + describe file('/etc/letsencrypt/dns-cloudflare.ini') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_grouped_into 'root' } + it { is_expected.to be_mode 400 } + end +end diff --git a/spec/classes/plugin/dns_cloudflare_spec.rb b/spec/classes/plugin/dns_cloudflare_spec.rb new file mode 100644 index 00000000..5eba736e --- /dev/null +++ b/spec/classes/plugin/dns_cloudflare_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'letsencrypt::plugin::dns_cloudflare' do + on_supported_os.each do |os, os_facts| + context "on #{os} based operating systems" do + let(:facts) { os_facts } + let(:params) { { 'api_token' => 'dummy-cloudflare-api-token' } } + let(:pre_condition) do + <<-PUPPET + class { 'letsencrypt': + email => 'foo@example.com', + } + PUPPET + end + let(:package_name) do + osname = facts[:os]['name'] + osrelease = facts[:os]['release']['major'] + osfull = "#{osname}-#{osrelease}" + if %w[RedHat-7 CentOS-7].include?(osfull) + 'python2-certbot-dns-cloudflare' + elsif %w[Debian RedHat].include?(facts[:os]['family']) + 'python3-certbot-dns-cloudflare' + elsif %w[FreeBSD].include?(facts[:os]['family']) + 'py38-certbot-dns-cloudflare' + end + end + + context 'with required parameters' do + it do + if package_name.nil? + is_expected.not_to compile + else + is_expected.to compile.with_all_deps + end + end + + describe 'with manage_package => true' do + let(:params) { super().merge(manage_package: true) } + + it do + if package_name.nil? + is_expected.not_to compile + else + is_expected.to contain_class('letsencrypt::plugin::dns_cloudflare').with_package_name(package_name) + is_expected.to contain_package(package_name).with_ensure('installed') + end + end + end + + describe 'with manage_package => false' do + let(:params) { super().merge(manage_package: false, package_name: 'dns-cloudflare-package') } + + it { is_expected.not_to contain_package('dns-cloudflare-package') } + end + end + end + end +end diff --git a/spec/defines/letsencrypt_certonly_spec.rb b/spec/defines/letsencrypt_certonly_spec.rb index 92efe5fb..e64ccf17 100644 --- a/spec/defines/letsencrypt_certonly_spec.rb +++ b/spec/defines/letsencrypt_certonly_spec.rb @@ -44,7 +44,7 @@ end it { is_expected.to contain_exec('initialize letsencrypt') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com') } - it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless "/usr/local/sbin/letsencrypt-domain-validation #{pathprefix}/etc/letsencrypt/live/foo.example.com/cert.pem 'foo.example.com'" } + it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless(['test ! -f /usr/local/sbin/letsencrypt-domain-validation', "/usr/local/sbin/letsencrypt-domain-validation #{pathprefix}/etc/letsencrypt/live/foo.example.com/cert.pem 'foo.example.com'"]) } end context 'with ensure absent' do @@ -186,6 +186,27 @@ class { 'letsencrypt::plugin::nginx': it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a nginx --cert-name 'foo.example.com' -d 'foo.example.com'" } end + context 'with dns-cloudflare plugin' do + let(:title) { 'foo.example.com' } + let(:params) { { plugin: 'dns-cloudflare', letsencrypt_command: 'letsencrypt' } } + let(:pre_condition) do + <<-PUPPET + class { 'letsencrypt': + email => 'foo@example.com', + config_dir => '/etc/letsencrypt', + } + class { 'letsencrypt::plugin::dns_cloudflare': + package_name => 'irrelevant', + api_token => 'dummy-cloudflare-api-token', + } + PUPPET + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('letsencrypt::plugin::dns_cloudflare') } + it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a dns-cloudflare --cert-name 'foo.example.com' -d 'foo.example.com' --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/dns-cloudflare.ini --dns-cloudflare-propagation-seconds 10" } + end + context 'with custom plugin' do let(:title) { 'foo.example.com' } let(:params) { { plugin: 'apache' } } @@ -462,7 +483,7 @@ class { 'letsencrypt::plugin::nginx': it { is_expected.to compile.with_all_deps } it { is_expected.to contain_file('/foo/bar/baz').with_ensure('directory') } - it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless '/usr/local/sbin/letsencrypt-domain-validation /foo/bar/baz/live/foo.example.com/cert.pem \'foo.example.com\'' } + it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless(['test ! -f /usr/local/sbin/letsencrypt-domain-validation', '/usr/local/sbin/letsencrypt-domain-validation /foo/bar/baz/live/foo.example.com/cert.pem \'foo.example.com\'']) } end context 'on FreeBSD', if: facts[:os]['name'] == 'FreeBSD' do @@ -474,7 +495,7 @@ class { 'letsencrypt::plugin::nginx': it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini email foo@example.com') } it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini server https://acme-v02.api.letsencrypt.org/directory') } it { is_expected.to contain_file('/usr/local/etc/letsencrypt').with_ensure('directory') } - it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless '/usr/local/sbin/letsencrypt-domain-validation /usr/local/etc/letsencrypt/live/foo.example.com/cert.pem \'foo.example.com\'' } + it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless(['test ! -f /usr/local/sbin/letsencrypt-domain-validation', '/usr/local/sbin/letsencrypt-domain-validation /usr/local/etc/letsencrypt/live/foo.example.com/cert.pem \'foo.example.com\'']) } end end end