From c0342f829e372e739a778377ac9c9e27fe6b0c51 Mon Sep 17 00:00:00 2001 From: Zack Steinkamp Date: Mon, 25 Mar 2024 11:30:08 -0700 Subject: [PATCH] fixes #50 - purge cached cert/key when a replacement is received (#51) --- src/index.ts | 4 +++ src/utils.ts | 78 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index ce930db..224d7ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ import { KEY_SUFFIX, CERTIFICATE_SUFFIX, CERTIFICATE_REQ_SUFFIX, + purgeCachedCertKey, } from './utils' import { AcmeClient } from './client' import fs from 'fs' @@ -203,6 +204,9 @@ async function clientAutoModeInternal( certInfo = await readCertificateInfo(certificatePem) fs.writeFileSync(certPath, certificatePem) log.info(`Wrote certificate to ${certPath}`) + + // Purge the cert/key in the shared dict zone if applicable + purgeCachedCertKey(r) } retVal.success = true diff --git a/src/utils.ts b/src/utils.ts index be73b48..1726e83 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -991,37 +991,40 @@ export function readKey(r: NginxHTTPRequest): string { * Given a request and suffix that indicates whether the caller wants the cert * or key, return the requested object from cache if possible, falling back to * disk. - * @param {NginxHTTPRequest} r - The Nginx HTTP request object. - * @param {string} suffix - The file suffix that indicates whether we want a cert or key - * @returns {string} - The contents of the cert or key + * @param {NginxHTTPRequest} r The Nginx HTTP request object. + * @param {string} suffix The file suffix that indicates whether we want a cert or key + * @returns {string} The contents of the cert or key */ function readCertOrKey( r: NginxHTTPRequest, suffix: typeof CERTIFICATE_SUFFIX | typeof KEY_SUFFIX ): string { let data = '' - const prefix = acmeDir(r) - const commonName = acmeCommonName(r) - const zone = acmeZoneName(r) - const path = joinPaths(prefix, commonName + suffix) - const key = ['acme', path].join(':') + const base = certOrKeyBase(r) + const path = base + suffix + const key = cacheKey(path) - // if the zone is not defined in nginx.conf, then we will bypass the cache - const cache = zone && ngx.shared && ngx.shared[zone] + const cache = ngxSharedDict(r) + // ensure the shared dict zone is configured before checking cache if (cache) { data = (cache.get(key) as string) || '' if (data) { + // Return cached value return data } } + + // filesystem fallback try { data = fs.readFileSync(path, 'utf8') } catch (e) { log.error('error reading from file:', path, `. Error=${e}`) return '' } + // try caching value read from disk in the shared dict zone, if configured if (cache && data) { + const zone = acmeZoneName(r) try { cache.set(key, data) log.debug(`wrote to cache: ${key} zone: ${zone}`) @@ -1032,3 +1035,58 @@ function readCertOrKey( } return data } + +/** + * Returns the NGINX shared dict zone if configured. + * @param r - The request or periodic session + * @returns Shared dict zone or `null` + */ +function ngxSharedDict( + r: NginxHTTPRequest | NginxPeriodicSession +): NgxSharedDict | null { + const zone = acmeZoneName(r) + if (zone && ngx.shared) { + const sharedZone = ngx.shared[zone] + if (sharedZone) { + return sharedZone + } + } + return null +} + +/** + * Removes cached cert and key from the shared dict zone, if applicable. + * @param r - The request or periodic session + */ +export function purgeCachedCertKey( + r: NginxHTTPRequest | NginxPeriodicSession +): void { + const objPrefix = certOrKeyBase(r) + const cache = ngxSharedDict(r) + + if (cache) { + cache.delete(cacheKey(objPrefix + CERTIFICATE_SUFFIX)) + cache.delete(cacheKey(objPrefix + KEY_SUFFIX)) + } +} + +/** + * Prepend our namespace to a given cache key + * @param key Path to the cert or key + * @returns Shared dict cache ke + */ +function cacheKey(key: string) { + return 'acme:' + key +} + +/** + * Returns the base path to store a cert or key + * @param path Path to the cert or key + * @returns Path string + */ +function certOrKeyBase(r: NginxHTTPRequest | NginxPeriodicSession): string { + const prefix = acmeDir(r) + const commonName = acmeCommonName(r) + const path = joinPaths(prefix, commonName) + return path +}