Let an explicit --days or --valid-to outrank the ARI window

ARI has overridden Le_NextRenewTime unconditionally since 3.1.4, so a user
who passed --days never got the schedule they asked for, and --valid-to was
guarded at issue time but not on the renewal check: the guard survived one
run before the next cron rewrote it and saved it back.

An explicit --days or --valid-to now pins the schedule. The window is still
taken when it is earlier than what the user asked for, so a CA can pull an
urgent renewal forward but can never push a pinned renewal back.
Le_RenewalDays is only written to the domain conf when --days was actually
passed, so its presence there is what marks a schedule as pinned.

A fixed-date --valid-to opts out of ARI entirely: that cert is not renewed
automatically at all, so pulling it forward would change what it does, not
just when it renews.

Both call sites go through the new _calc_ari_renew_time.
This commit is contained in:
neil committed 2026-09-01 20:26:34 +08:00
1 parent 4a3f8b5dea
commit 7b5a902755
1 file changed
+77 -19
+77 -19
View File
@@ -2033,6 +2033,32 @@ _calc_validto_renew_time() {
fi fi
} }
#Usage: _calc_ari_renew_time aristarttime ariendtime now currenttime pinned
#Prints the renew time to take from the CA's ARI suggestedWindow, or nothing
#when the window must be ignored. The point inside the window is derived from
#the current time rather than its start, so renewals spread out across the
#network instead of all firing at the same instant.
#A schedule the user pinned with --days or --valid-to only yields to a window
#that is EARLIER than what the user asked for: the CA can still pull an urgent
#renewal forward, but it can never push a pinned renewal back.
_calc_ari_renew_time() {
_cart_start="$1"
_cart_end="$2"
_cart_now="$3"
_cart_current="$4"
_cart_pinned="$5"
if [ -z "$_cart_start" ] || [ -z "$_cart_end" ] || [ "$_cart_end" -le "$_cart_start" ]; then
return 0
fi
_cart_window=$(_math "$_cart_end" - "$_cart_start")
_cart_offset=$(_math "$_cart_now" % "$_cart_window")
_cart_next=$(_math "$_cart_start" + "$_cart_offset")
if [ "$_cart_pinned" ] && [ "$_cart_current" ] && [ "$_cart_next" -ge "$_cart_current" ]; then
return 0
fi
printf "%s" "$_cart_next"
}
_mktemp() { _mktemp() {
if _exists mktemp; then if _exists mktemp; then
if mktemp 2>/dev/null; then if mktemp 2>/dev/null; then
@@ -6150,10 +6176,16 @@ $_authorizations_map"
Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime") Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime")
_savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
# Le_RenewalDays is only written to the domain conf when the user actually
# passed --days; the default schedule is never saved. That is what makes the
# presence of the value a reliable "the user pinned this" flag, here and on
# every later renewal check.
if [ -z "$Le_RenewalDays" ]; then if [ -z "$Le_RenewalDays" ]; then
Le_RenewalDays="$DEFAULT_RENEW" Le_RenewalDays="$DEFAULT_RENEW"
_ari_pinned=""
else else
_savedomainconf "Le_RenewalDays" "$Le_RenewalDays" _savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
_ari_pinned="1"
fi fi
if [ "$CA_BUNDLE" ]; then if [ "$CA_BUNDLE" ]; then
@@ -6227,15 +6259,25 @@ $_authorizations_map"
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
fi fi
# RFC 9773 ARI: if the CA exposes renewalInfo, override Le_NextRenewTime # RFC 9773 ARI: if the CA exposes renewalInfo, take Le_NextRenewTime from
# with a time picked at random within the suggestedWindow. This both gives # the suggestedWindow. This gives the CA control over renewal scheduling and
# the CA full control over renewal scheduling and disperses renewals across # disperses renewals across the network so all clients don't hit the CA at
# the network so all clients don't hit the CA at the same instant. # the same instant.
# An explicit --days or --valid-to wins over the window, except when the CA
# wants the cert renewed EARLIER than the user asked for: an urgent renewal
# must still get through. A fixed-date --valid-to opts out entirely, because
# there the cert is pinned to an expiry and is not renewed automatically at
# all -- letting ARI pull it forward would change that, not just its timing.
# Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and fall back to # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and fall back to
# the legacy time-based renewal calculation. # the legacy time-based renewal calculation.
if [ "$_notAfter" ]; then
_ari_pinned="1"
fi
if [ "$NO_ARI" = "1" ]; then if [ "$NO_ARI" = "1" ]; then
_debug "NO_ARI=1, skipping ARI suggestedWindow override" _debug "NO_ARI=1, skipping ARI suggestedWindow override"
elif [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ] && [ -z "$_notAfter" ]; then elif [ "$_valid_to" ] && ! _startswith "$_valid_to" "+"; then
_debug "Fixed --valid-to, skipping ARI suggestedWindow override"
elif [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ]; then
_ari_resp_new="$(_get_ARI "$CERT_PATH")" _ari_resp_new="$(_get_ARI "$CERT_PATH")"
_debug2 "_ari_resp_new" "$_ari_resp_new" _debug2 "_ari_resp_new" "$_ari_resp_new"
_ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')" _ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
@@ -6243,13 +6285,15 @@ $_authorizations_map"
if [ "$_ari_start_new" ] && [ "$_ari_end_new" ]; then if [ "$_ari_start_new" ] && [ "$_ari_end_new" ]; then
_ari_start_t_new="$(_date2time "$(echo "$_ari_start_new" | sed 's/\.[0-9]*//')")" _ari_start_t_new="$(_date2time "$(echo "$_ari_start_new" | sed 's/\.[0-9]*//')")"
_ari_end_t_new="$(_date2time "$(echo "$_ari_end_new" | sed 's/\.[0-9]*//')")" _ari_end_t_new="$(_date2time "$(echo "$_ari_end_new" | sed 's/\.[0-9]*//')")"
if [ "$_ari_start_t_new" ] && [ "$_ari_end_t_new" ] && [ "$_ari_end_t_new" -gt "$_ari_start_t_new" ]; then _ari_next_new="$(_calc_ari_renew_time "$_ari_start_t_new" "$_ari_end_t_new" "$(_time)" "$Le_NextRenewTime" "$_ari_pinned")"
_ari_window=$(_math "$_ari_end_t_new" - "$_ari_start_t_new") if [ "$_ari_next_new" ]; then
_ari_offset=$(_math "$(_time)" % "$_ari_window") Le_NextRenewTime="$_ari_next_new"
Le_NextRenewTime=$(_math "$_ari_start_t_new" + "$_ari_offset")
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
_info "ARI suggestedWindow: $(__green "$_ari_start_new") to $(__green "$_ari_end_new")" _info "ARI suggestedWindow: $(__green "$_ari_start_new") to $(__green "$_ari_end_new")"
_info "Next renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")" _info "Next renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
elif [ "$_ari_pinned" ]; then
_info "ARI suggestedWindow: $(__green "$_ari_start_new") to $(__green "$_ari_end_new")"
_info "It is later than the renewal time you asked for, keeping: $(__green "$Le_NextRenewTimeStr")"
fi fi
fi fi
fi fi
@@ -6392,10 +6436,21 @@ renew() {
# ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check. # ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check.
# If the window has started, renew now even if Le_NextRenewTime is in the future. # If the window has started, renew now even if Le_NextRenewTime is in the future.
# Le_RenewalDays and Le_Valid_To are only in the domain conf when the user
# passed --days or --valid-to, so their presence is what pins the schedule.
# A pinned schedule still yields to a window that is EARLIER than it, so the
# CA can pull an urgent renewal forward. A fixed-date --valid-to opts out
# entirely: that cert is not renewed automatically at all.
# Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only
# Le_NextRenewTime for the renewal decision. # Le_NextRenewTime for the renewal decision.
_ari_pinned=""
if [ "$Le_RenewalDays" ] || [ "$Le_Valid_To" ]; then
_ari_pinned="1"
fi
if [ "$NO_ARI" = "1" ]; then if [ "$NO_ARI" = "1" ]; then
_debug "NO_ARI=1, skipping ARI suggestedWindow check" _debug "NO_ARI=1, skipping ARI suggestedWindow check"
elif [ "$Le_Valid_To" ] && ! _startswith "$Le_Valid_To" "+"; then
_debug "Fixed --valid-to, skipping ARI suggestedWindow check"
elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then
if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then
_ari_resp="$(_get_ARI "$CERT_PATH")" _ari_resp="$(_get_ARI "$CERT_PATH")"
@@ -6414,16 +6469,19 @@ renew() {
_debug "Le_NextRenewTime" "$Le_NextRenewTime" _debug "Le_NextRenewTime" "$Le_NextRenewTime"
# Update ARI if needed # Update ARI if needed
if [ "$_ari_start_t" ] && [ "$_ari_end_t" ] && [ "$Le_NextRenewTime" ] && [ "$_ari_end_t" -gt "$_ari_start_t" ] && ([ "$Le_NextRenewTime" -lt "$_ari_start_t" ] || [ "$Le_NextRenewTime" -gt "$_ari_end_t" ]); then if [ "$_ari_start_t" ] && [ "$_ari_end_t" ] && [ "$Le_NextRenewTime" ] && [ "$_ari_end_t" -gt "$_ari_start_t" ] && ([ "$Le_NextRenewTime" -lt "$_ari_start_t" ] || [ "$Le_NextRenewTime" -gt "$_ari_end_t" ]); then
_ari_old_time_str="$Le_NextRenewTimeStr" _ari_next="$(_calc_ari_renew_time "$_ari_start_t" "$_ari_end_t" "$(_time)" "$Le_NextRenewTime" "$_ari_pinned")"
_info "Current renewal time: $(__green "$_ari_old_time_str")" if [ "$_ari_next" ]; then
_ari_window=$(_math "$_ari_end_t" - "$_ari_start_t") _ari_old_time_str="$Le_NextRenewTimeStr"
_ari_offset=$(_math "$(_time)" % "$_ari_window") _info "Current renewal time: $(__green "$_ari_old_time_str")"
Le_NextRenewTime=$(_math "$_ari_start_t" + "$_ari_offset") Le_NextRenewTime="$_ari_next"
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
_info "ARI suggestedWindow: $(__green "$_ari_start") to $(__green "$_ari_end")" _info "ARI suggestedWindow: $(__green "$_ari_start") to $(__green "$_ari_end")"
_info "Updating renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")" _info "Updating renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
_savedomainconf Le_NextRenewTime "$Le_NextRenewTime" _savedomainconf Le_NextRenewTime "$Le_NextRenewTime"
_savedomainconf Le_NextRenewTimeStr "$Le_NextRenewTimeStr" _savedomainconf Le_NextRenewTimeStr "$Le_NextRenewTimeStr"
else
_debug "ARI wants a later renewal than --days/--valid-to asked for, keeping $Le_NextRenewTimeStr"
fi
fi fi
if [ "$Le_NextRenewTime" ] && [ "$(_time)" -ge "$Le_NextRenewTime" ]; then if [ "$Le_NextRenewTime" ] && [ "$(_time)" -ge "$Le_NextRenewTime" ]; then
_info "ARI suggested renewal has passed ($(__green "$Le_NextRenewTimeStr")), proceeding with renewal." _info "ARI suggested renewal has passed ($(__green "$Le_NextRenewTimeStr")), proceeding with renewal."
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.