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