From bee364eccce12cb27af9b28543f3a3bef13f5990 Mon Sep 17 00:00:00 2001
From: Fabien COMBERNOUS <fabien.combernous@adullact.org>
Date: Fri, 4 Nov 2022 18:48:22 +0100
Subject: [PATCH] add generated crl

---
 manifests/ca/root.pp               |  6 ++--
 manifests/init.pp                  | 56 +++++++++++++++++++++++++++++-
 spec/acceptance/cfssl_spec.rb      |  6 ++++
 templates/cfssl-gencrl.service.epp |  9 +++++
 templates/cfssl-gencrl.sh.epp      | 24 +++++++++++++
 templates/cfssl-gencrl.timer.epp   |  9 +++++
 6 files changed, 106 insertions(+), 4 deletions(-)
 create mode 100644 templates/cfssl-gencrl.service.epp
 create mode 100644 templates/cfssl-gencrl.sh.epp
 create mode 100644 templates/cfssl-gencrl.timer.epp

diff --git a/manifests/ca/root.pp b/manifests/ca/root.pp
index cb424f4..b323aa4 100644
--- a/manifests/ca/root.pp
+++ b/manifests/ca/root.pp
@@ -4,13 +4,13 @@
 #
 # @example
 #   include cfssl::ca::root
+#
 class cfssl::ca::root (
   Hash $subject = { 'C' => 'FR', 'L' => 'MONTPELLIER', 'O' => 'EXEMPLE ORG', 'OU' => 'IT Dept' },
   String[1] $cn = 'EXEMPLE ROOT CA GEN1',
   String[1] $expiry = '43800h',
   Cfssl::Ca::Key $key = { algo => 'rsa', size => 2048 },
 ) {
-  $_rootca_filename = 'ROOT_ca'
   $_rootca_csr = {
     cn    => $cn,
     names => [$subject],
@@ -21,8 +21,8 @@ class cfssl::ca::root (
 
   exec { "initca ${cn}":
     path    => "/usr/bin:${cfssl::binpath}",
-    command => "echo '${_rootca_csr_json}' | cfssl gencert -initca - | cfssljson -bare ${cfssl::confdir}/ca/${_rootca_filename}",
-    creates => "${cfssl::confdir}/ca/${_rootca_filename}-key.pem",
+    command => "echo '${_rootca_csr_json}' | cfssl gencert -initca - | cfssljson -bare ${cfssl::confdir}/ca/${cfssl::serve_ca}",
+    creates => "${cfssl::confdir}/ca/${cfssl::serve_ca}-key.pem",
     user    => $cfssl::sysuser,
   }
 }
diff --git a/manifests/init.pp b/manifests/init.pp
index daa922f..506b18f 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -4,6 +4,9 @@
 #
 # @example
 #   include cfssl
+#
+# @param crl_expiry A value, in seconds, after which the CRL should expire from the moment of the request
+#
 class cfssl (
   Hash $rootca_manifest = {},
   Stdlib::HTTPSUrl $downloadurl = 'https://github.com/cloudflare/cfssl/releases/download',
@@ -23,6 +26,10 @@ class cfssl (
   Stdlib::Absolutepath $binpath = '/usr/local/bin',
   Cfssl::Serveconfig $serve_config = { signing => { 'default' => { expiry => '1h', usages => ['client auth'] } } },
   String[1] $serve_ca = 'ROOT_ca',
+  Boolean $crl_manage = true,
+  Stdlib::Absolutepath $crldir = '/var/cfssl',
+  Integer $crl_expiry = 604800,
+  String[1] $crl_gentimer = '*:00:00',
 ) {
   include cfssl::goose
   include postgresql::server
@@ -32,6 +39,8 @@ class cfssl (
   $_goose_cfssldbmigrate_path = "/home/${sysuser}/goose-cfssldbmigrate"
   $_serve_config_json = 'serve-config.json'
   $_db_config_json = 'db-config.json'
+  $_systemd_unitdir = '/etc/systemd/system'
+  $_crlunits = ['cfssl-gencrl.service', 'cfssl-gencrl.timer']
 
   group { $sysgroup :
     ensure => present,
@@ -76,7 +85,7 @@ class cfssl (
     }
   }
 
-  file { [$confdir, "${confdir}/ca", $logdir]:
+  file { [$confdir, "${confdir}/ca", $logdir, $crldir]:
     ensure  => directory,
     mode    => '0700',
     owner   => $sysuser,
@@ -163,4 +172,49 @@ class cfssl (
     subscribe => Archive["${binpath}/cfssl"],
     provider  => 'systemd',
   }
+
+  if $cfssl::crl_manage {
+    ensure_packages(['jq','coreutils'], { ensure => 'present' })
+
+    file { "${cfssl::binpath}/cfssl-gencrl.sh":
+      ensure  => file,
+      mode    => '0755',
+      owner   => 0,
+      group   => 0,
+      content => epp('cfssl/cfssl-gencrl.sh.epp'),
+    }
+
+    $_crlunits.each | String $_crlunit | {
+      file { "${_systemd_unitdir}/${_crlunit}":
+        ensure  => file,
+        mode    => '0644',
+        owner   => 0,
+        group   => 0,
+        content => epp("cfssl/${$_crlunit}.epp"),
+      }
+      ~> service { $_crlunit:
+        ensure   => 'running',
+        enable   => true,
+        require  => [
+          File["${cfssl::binpath}/cfssl-gencrl.sh"],
+          Service['cfssl'],
+        ],
+        provider => 'systemd',
+      }
+    }
+  } else {
+    $_crlunits.each | String $_crlunit | {
+      service { $_crlunit:
+        ensure   => 'stopped',
+        enable   => false,
+        provider => 'systemd',
+      }
+      -> file { "${_systemd_unitdir}/${_crlunit}":
+        ensure  => absent,
+      }
+    }
+    file { "${cfssl::binpath}/cfssl-gencrl.sh":
+      ensure  => absent,
+    }
+  }
 }
diff --git a/spec/acceptance/cfssl_spec.rb b/spec/acceptance/cfssl_spec.rb
index 86ad2d9..fc61303 100644
--- a/spec/acceptance/cfssl_spec.rb
+++ b/spec/acceptance/cfssl_spec.rb
@@ -20,5 +20,11 @@ describe 'cfssl' do
     describe command('curl -s -d "{}" -H "Content-Type: application/json" -X POST 127.0.0.1:8080/api/v1/cfssl/info') do
       its(:stdout) { is_expected.to match %r{BEGIN CERTIFICATE} }
     end
+    describe command('openssl crl -in /var/cfssl/crl.pem -text -noout') do
+      # rubocop:disable RSpec/RepeatedDescription
+      its(:stdout) { is_expected.to match %r{Certificate Revocation List } }
+      its(:stdout) { is_expected.to match %r{Issuer: C = FR, L = MONTPELLIER, O = EXEMPLE ORG, OU = IT Dept, CN = EXEMPLE ROOT CA GEN1} }
+      its(:stdout) { is_expected.to match %r{No Revoked Certificates} }
+    end
   end
 end
diff --git a/templates/cfssl-gencrl.service.epp b/templates/cfssl-gencrl.service.epp
new file mode 100644
index 0000000..232b1a0
--- /dev/null
+++ b/templates/cfssl-gencrl.service.epp
@@ -0,0 +1,9 @@
+[Unit]
+Description=CloudFlare's PKI CRL generator
+Requires=network-online.target
+After=cfssl.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=<%= $cfssl::binpath %>/cfssl-gencrl.sh
diff --git a/templates/cfssl-gencrl.sh.epp b/templates/cfssl-gencrl.sh.epp
new file mode 100644
index 0000000..980a73a
--- /dev/null
+++ b/templates/cfssl-gencrl.sh.epp
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+TMPFILE="/tmp/crl${$}.pem"
+
+echo "-----BEGIN X509 CRL-----" > $TMPFILE
+curl -s -d '{}' -H "Content-Type: application/json" -X GET <%= $cfssl::binding_ip %>:<%= $cfssl::port %>/api/v1/cfssl/crl?expiry=<%= $cfssl::crl_expiry %>s | jq -r '.result' | fold -w 64 >> $TMPFILE
+echo "-----END X509 CRL-----" >> $TMPFILE
+
+if [ -s $TMPFILE ]
+then
+  if openssl crl -in $TMPFILE -text -noout | grep -qP '^Certificate Revocation List '
+  then
+    # the temp file looks as expected, we move it to crldir
+    cp $TMPFILE <%= $cfssl::crldir %>/crl.pem
+  else
+    echo "gencrl : missing header 'Certificate Revocation List'"
+    exit 1
+  fi
+else
+  echo "gencrl : empty file"
+  exit 1
+fi
+
+
diff --git a/templates/cfssl-gencrl.timer.epp b/templates/cfssl-gencrl.timer.epp
new file mode 100644
index 0000000..61c8970
--- /dev/null
+++ b/templates/cfssl-gencrl.timer.epp
@@ -0,0 +1,9 @@
+[Unit]
+Description=CloudFlare's PKI CRL generator
+
+[Timer]
+OnCalendar=*-*-* <%= $cfssl::crl_gentimer %>
+Persistent=true
+
+[Install]
+WantedBy=timers.target
-- 
GitLab