From 71eecc0be959095c49a4dbb613447883315c9c9b Mon Sep 17 00:00:00 2001
From: Thomas Bellman <bellman@nsc.liu.se>
Date: Mon, 25 Sep 2023 23:53:20 +0200
Subject: [PATCH] Configure fetch-crl to cache for short times.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We have found a problem with some CA CRL distribution points and the
fetch-crl caching logic.  By default, fetch-crl caches the revocation
lists it downloads (in /var/cache/fetch-crl), and keeps using that
cached data until it is deemed no longer "fresh", and only then does
it download a new updated CRL.  The limit on freshness is based on the
Expires: and Cache-Control: headers that the CRL web server sends.

The problem is, if the CRL servers sets Expires: to the same time that
the CRL expires (the nextUpdate field of the CRL), fetch-crl will not
download a new CRL until the cached CRL is no longer valid.

This has apparently not been a big practical problem until now, as
fetch-crl by default sets an upper limit of 4 days (96 hours) for how
long it caches CRLs.  Since most CRLs have lifetimes longer than that
(seven days is common), the cached data expires well before the CRL
expires, and thus a new CRL will be downloaded in time.  Also, some
CRL distribution points also send a Cache-Control: header with a
max-age parameter of just a few hours, further limiting how long
fetch-crl will cache those CRLs.

The problem was noticed when a new GÉANT TCS CA was created, which
gives out CRLs with a lifetime of just 2 days (48 hours), and just
sets Expires: with end date equal to the CRL nextUpdate field.
Suddenly web servers would get expired CRLs, and lock out users.

Also, even if the CRLs have longer lifetimes than 4 days, having
fetch-crl use up to four days old CRLs means that revocations are
not propagated in a timely manner.  Most users probably prefer if
a revoked certificate becomes unusable much sooner than 4 days.

We work around this by configuring fetch-crl to have a maximum cache
lifetime of only one hour.  While the default fetch-crl cron job only
runs every six hours, it should still ensure CRLs are refreshed in a
reasonably timely manner.

We set the 'maxcachetime' parameter in the main /etc/fetch-crl.conf
config file.  Users can then override our choice using the
x509certs::fetchcrl::option definition.
---
 manifests/fetchcrl.pp            |  1 +
 manifests/fetchcrl/shortcache.pp | 39 ++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)
 create mode 100644 manifests/fetchcrl/shortcache.pp

diff --git a/manifests/fetchcrl.pp b/manifests/fetchcrl.pp
index 21a119b..4139c37 100644
--- a/manifests/fetchcrl.pp
+++ b/manifests/fetchcrl.pp
@@ -12,6 +12,7 @@ class x509certs::fetchcrl
 {
     contain x509certs::fetchcrl::package
     contain x509certs::fetchcrl::cfgdir
+    contain x509certs::fetchcrl::shortcache
     contain x509certs::fetchcrl::service
     contain x509certs::fetchcrl::initial
 }
diff --git a/manifests/fetchcrl/shortcache.pp b/manifests/fetchcrl/shortcache.pp
new file mode 100644
index 0000000..7170678
--- /dev/null
+++ b/manifests/fetchcrl/shortcache.pp
@@ -0,0 +1,39 @@
+# Copyright © 2023      National Supercomputer Centre,
+#                       Linköping University, Sweden
+# Licensed under the GNU LGPL v3+; see the README file for more information.
+
+
+# Internal helper for x509certs::fetchcrl class.
+#
+# This class sets the maxcachetime option in the general section to
+# some resonably short time (default 1 hour).  That is done to work
+# around breakage in fetch-crl's caching logic, where it can otherwise
+# keep using an old CRL up until it expires.  This can happen when the
+# web server publishing the CRL sets the Expire: header to the same as
+# the nextUpdate field in the CRL.  If fetch-crl then runs slightly
+# before the CRL expires, it will use its cached CRL instead of down-
+# loading a fresh CRL from the source.  And then just a few minutes
+# later, the CRL expires, but the fetch-crl cron job doesn't run again
+# until several hours later (the default cron job runs every six hours).
+#
+# By setting a short maxcachetime, we increase the likelyhood that
+# fetch-crl actually runs and downloads a new CRL before the old CRL
+# expires.
+#
+# We set the maxcachetime option in the main /etc/fetch-crl.conf
+# config file, so users can override the option using a normal
+# x509certs::fetchcrl::option resource declaration.
+#
+class x509certs::fetchcrl::shortcache($maxcachetime = 1*60*60)
+{
+    ensure_line {
+	'x509certs::fetchcrl::shortcache':
+	    file => '/etc/fetch-crl.conf',
+	    line => "maxcachetime = ${maxcachetime}",
+	    pattern => '^maxcachetime(\s*=.*)?$',
+	    # This makes sure the line is added before any trust anchor section
+	    where => '^(\s*\[.*|\s*$|\s*[^#;].*)',
+	    addhow => 'prepend',
+	    notify => Class['x509certs::fetchcrl::initial'];
+    }
+}
-- 
GitLab